Java Text Block Formatting with Variables and Placeholders

Java text block formatting with variables using formatted(), String.format(), MessageFormat and named placeholders, plus what happened to string templates.

A text block with %s, %d and %.2f placeholders filled by formatted() with three arguments in order

Java text block formatting with variables works through placeholders, so we write %s and %d inside the text block and call formatted() on it with the values, because Java has no syntax for putting variables inside a string literal. The formatted() method arrived in Java 15 together with text blocks and works like String.format().

We fill text blocks at runtime for email and notification templates, JSON or XML payloads in tests, log messages and help texts that show a version number or a file name.

The following example fills an order confirmation with a customer name and an order number.

String name = "Lokesh";
int orderId = 1042;
String email = """
        Hi %s,
        your order %d is on its way.
        """.formatted(name, orderId);
boolean filled = email.equals("Hi Lokesh,\nyour order 1042 is on its way.\n");   // true

Notice that the placeholders keep the layout of the text block intact, and the values are passed in the order the placeholders appear. We also cover String.format(), MessageFormat, named placeholders, why concatenation breaks the layout, and what happened to string templates.

1. Text Block Formatting with String.formatted()

The formatted() method treats the text block as a format string and replaces each format specifier with the next argument. The common specifiers are %s for any value, %d for integers and %.2f for decimals with two digits.

A text block with %s, %d and %.2f placeholders filled by formatted() with three arguments in order
The formatted() method replaces each placeholder with the next argument, in order, and keeps the layout of the text block

A JSON request body in an integration test is a typical case. The structure stays readable, and only the values change between tests.

String json = """
        {
          "orderId": %d,
          "customer": "%s",
          "total": %.2f
        }
        """.formatted(1042, "Lokesh", 18.5);
boolean hasTotal = json.contains("\"total\": 18.50");        // true (English default locale)

The formatted() method uses the default locale of the JVM, so %.2f prints 18,50 on a machine with a German locale and breaks the JSON. For machine-readable output, we call String.format(Locale.ROOT, template, args) instead, which does the same work with a fixed locale.

String template = """
        "total": %.2f
        """;
String safe = String.format(Locale.ROOT, template, 18.5);
boolean dot = safe.contains("18.50");                        // true

1.1. Passing the Arguments as an Array

The parameter of formatted() is a varargs Object…, so an Object[] works too. This helps when the values are collected in a loop or come from another method.

Object[] values = { 1042, "Lokesh" };
String line = "Order %d for %s".formatted(values);          // "Order 1042 for Lokesh"

1.2. Reusing an Argument by Its Index

A specifier such as %1$s refers to the first argument by position, so a value that appears twice in the template is passed only once.

String reminder = """
        Hi %1$s, your order %2$d is ready.
        Thank you for shopping with us, %1$s!""".formatted("Lokesh", 1042);
boolean twice = reminder.lines().allMatch(l -> l.contains("Lokesh"));   // true

1.3. Percent Signs and Format Errors

Every % in the text block starts a specifier, so a literal percent sign is written as %%. A missing argument or an argument of the wrong type throws an exception when formatted() runs, not at compile time.

String sale = "Save %d%% on %s".formatted(20, "Tea");       // "Save 20% on Tea"
String missing = "%s and %s".formatted("Tea");               // MissingFormatArgumentException
String wrongType = "%d cups".formatted("two");               // IllegalFormatConversionException
String extra = "%s".formatted("Tea", "Coffee");              // "Tea"

Extra arguments are ignored without an error, so a test that checks the full output is the best guard against a template and its arguments drifting apart.

1.4. Calling String.format() Instead

The call template.formatted(args) gives the same result as String.format(template, args). We use String.format() when we need the overload with a Locale, as in section 1, or when the code base still targets Java 14 or older.

String sameText = String.format("Order %d for %s", 1042, "Lokesh");   // "Order 1042 for Lokesh"

2. Text Block Formatting with MessageFormat.format()

The MessageFormat class from java.text uses numbered placeholders such as {0} and {1}, and it formats numbers and dates for a locale. Resource bundles for translated messages use the same syntax, which makes it the usual choice for user-facing text in several languages.

Two characters have a special meaning in a MessageFormat pattern. Curly braces mark placeholders, so literal braces must be quoted as ‘{‘ and ‘}’, and a single apostrophe starts a quoted section, so a literal apostrophe is written as ”.

String json = """
        '{'
          "orderId": {0,number,#},
          "customer": "{1}"
        '}'
        """;
String body = MessageFormat.format(json, 1042, "Lokesh");
boolean valid = body.startsWith("{\n  \"orderId\": 1042,");    // true
String broken = MessageFormat.format("It's {0}", "ready");     // "Its {0}"
String fixed = MessageFormat.format("It''s {0}", "ready");     // "It's ready"
String grouped = MessageFormat.format("{0} items", 1042);      // "1,042 items" (English default locale)

We can see why MessageFormat needs careful testing. A lone apostrophe swallows the placeholder without any error, and a plain {0} formats numbers with grouping separators, so we write {0,number,#} for IDs. For text with few braces and apostrophes and a single language, formatted() has fewer surprises.

3. Named Placeholders with a Map

Positional placeholders get hard to follow in a long template. An email service that loads its templates from files lets the content team write {customer} and {orderId} instead of %s and %d, and the code fills them from a Map.

static String fill(String template, Map<String, ?> values) {
    String result = template;
    for (Map.Entry<String, ?> entry : values.entrySet()) {
        result = result.replace("{" + entry.getKey() + "}", String.valueOf(entry.getValue()));
    }
    return result;
}
String shipped = """
        Hi {customer},
        order {orderId} was shipped today.""";
String text = fill(shipped, Map.of("customer", "Lokesh", "orderId", 1042));
boolean ready = text.equals("Hi Lokesh,\norder 1042 was shipped today.");   // true

The replace() method replaces literal text, not a regular expression, so values with $ or backslashes stay as they are. A placeholder without a value stays visible in the output, so a test that checks the output catches it. For conditions and loops inside templates, a template engine such as Thymeleaf or FreeMarker fits better than any string method.

4. String Concatenation (Do Not Try)

Joining text blocks and variables with + looks like interpolation, but each text block is a separate literal with its own line and whitespace rules.

String name = "Lokesh";
String greeting = """
        Hello, """ + name + """
        Welcome to Java text blocks!
        """;
boolean oneLine = greeting.equals("Hello,LokeshWelcome to Java text blocks!\n");   // true

The output has lost the space after the comma and the line break after the name. The first text block ends at its closing delimiter, so it contains Hello, without a line break, and the compiler strips the trailing space as incidental whitespace. The third text block starts with the line after its opening delimiter, so nothing separates the name from Welcome. Repairing it with \s and \n escapes defeats the purpose of a text block, so we use formatted().

5. What Happened to String Templates?

String templates were the planned interpolation feature, with the syntax STR.”Hi \{name}”. They were a preview in Java 21 (JEP 430) and Java 22 (JEP 459), and a third preview was proposed for Java 23 (JEP 465). That JEP was withdrawn, and the feature was removed from JDK 23 so the design could be reworked.

// does not compile: string templates were removed in JDK 23
String hi = STR."Hi \{name}";

As of Java 25, no replacement has shipped, so code that used STR on Java 21 or 22 with –enable-preview must move to formatted() or MessageFormat. The Java string templates post describes the preview design for readers who maintain such code.

6. Choosing a Formatting Method

The right method depends on who writes the template and who reads the result.

MethodPlaceholderBest forWatch out for
formatted()%s, %d, %.2fTemplates in code, Java 15+Default locale, literal % as %%
String.format(Locale, …)Same as formatted()Machine-readable outputLonger call
MessageFormat.format(){0}, {1,number,#}Translated user messagesApostrophes and braces must be quoted
replace() with a Map{name}Templates from files, long templatesNo number formatting
+ concatenationNoneSingle-line stringsBreaks text block layout

7. Text Block Formatting FAQs

Kotlin, JavaScript and Python have string interpolation, so the first surprise in Java is that a variable name inside a text block stays plain text.

7.1. Can I put variables inside a Java text block?

No. A text block is a literal, and Java has no interpolation syntax, so {name} inside it stays plain text. We use placeholders with formatted() or a Map, as in section 3.

7.2. Does Java have string interpolation?

Not as of Java 25. The string templates preview in Java 21 and 22 was withdrawn, so formatted(), String.format() and MessageFormat remain the standard options.

7.3. How do I escape a percent sign in formatted()?

We double it. The template “%d%%” prints 20%, while a single % followed by a letter that is not a valid conversion throws UnknownFormatConversionException.

7.4. How do I keep a JSON text block valid when a value contains quotes?

We escape the value or, better, build the JSON with a library such as Jackson. A customer name like Tim “TJ” Lee inserted with %s ends the JSON string early, so text block templates suit tests with known values more than production payloads.

7.5. Is formatted() slower than concatenation?

Yes, because Formatter parses the template on every call, while concatenation does not parse anything. For an email or a test fixture the difference does not matter, and in a hot loop we build the string with + or a StringBuilder.

8. Conclusion

Text blocks keep the layout of multiline text, and formatted() fills them with values through %s, %d and %.2f placeholders. Indexed specifiers reuse a value, %% prints a percent sign, and String.format(Locale.ROOT, …) keeps numbers stable in JSON and other machine-readable text.

For translated messages, MessageFormat adds locale-aware numbers at the price of quoting rules for apostrophes and braces, and a small Map based helper gives named placeholders. String templates are gone since JDK 23, so these methods are what current Java offers. The Java multiline string tutorial covers text block syntax and indentation, and the Java String tutorial links the related String topics.

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.