Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 4 – Arrays and strings in depth

Text blocks for multi-line strings

Write multi-line Java strings with text blocks: how the closing delimiter sets the indentation, escapes for trailing spaces and joined lines, and formatted().

  • Beginner
  • 15 minutes
  • Examples run with Java temurin-25.0.4+101.0.LTS
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Write a multi-line string as a text block, and say where its content starts and ends
  • Predict how much indentation each line keeps from the position of the closing delimiter
  • Use \s for trailing spaces, a backslash at the end of a line to join lines, and \""" for three quotes
  • Format a template with formatted(), and choose another tool for SQL, HTML and JSON built from outside values

Before you start

What you’ll learn · Before you start

What you will learn

  • Write a multi-line string as a text block, and say where its content starts and ends
  • Predict how much indentation each line keeps from the position of the closing delimiter
  • Use \s for trailing spaces, a backslash at the end of a line to join lines, and \""" for three quotes
  • Format a template with formatted(), and choose another tool for SQL, HTML and JSON built from outside values

Before you start

On this page

Written as an ordinary string literal, the one-line JSON template {"name": "%s", "city": "%s"} needs 8 backslashes, one in front of each double quote, and a three-line HTML e-mail needs a \n at the end of every line. A text block: A Java string literal that spans several lines: three double quotes and a line break open it, three double quotes close it, and the text between keeps its quotes and line breaks without escapes. Standard since JDK 15. holds such text as written: it opens with three double quotes and a line break, ends with three more, and keeps quotes and line breaks as they are. Text blocks are standard since JDK 15 (JEP 378). Two rules decide what the string contains: the compiler removes the indentation that only lines the text up with your code, measured from the least indented line, and it removes spaces at the ends of lines.

You will see both rules with every space made visible, step through where the closing quotes put the margin and why escapes come last, and use formatted() templates up to where they stop being safe. The exercise is a bill whose output must match to the last space.

A text block starts on the line after its opening quotes

A text block is an ordinary String, written differently. This e-mail template is one, filled in with formatted():

An e-mail template, filled in Java · Email.java
// An order confirmation e-mail, written as a text block and filled in with formatted().
void main() {
    String template = """
            <p>Namaste %s,</p>
            <p>Your order <b>%s</b> for ₹%d is confirmed.</p>
            <p>Delivery to: %s</p>
            """;
    String email = template.formatted("Asha", "ATF-1042", 1250, "Kothrud, Pune");
    IO.print(email);
    IO.println("lines: " + email.lines().count() + ", ends with a line break: " + email.endsWith("\n"));
}

Output

<p>Namaste Asha,</p>
<p>Your order <b>ATF-1042</b> for ₹1250 is confirmed.</p>
<p>Delivery to: Kothrud, Pune</p>
lines: 3, ends with a line break: true

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Email.java

  • formatted("Asha", "ATF-1042", 1250, "Kothrud, Pune") fills the %s and %d placeholders exactly as String.format(template, ...) would. The last section of the lesson shows where that stops being safe.
  • Every line ends with \n, the line feed, even if the source file was saved on Windows with \r\n: the compiler normalises line terminator: The character or pair of characters that ends a line of text: a line feed \n, a carriage return \r, or the pair \r\n that Windows programs write. String.lines() splits at all three and never keeps them. More in “The String API in depth” before anything else. The e-mail has 3 lines and ends with a line break, because the closing """ stands on a line of its own.
  • The quotes inside the HTML needed no escapes, and neither would quotes in JSON.

The content starts on the line after the opening quotes, and only spaces, tabs and form feeds may stand between the quotes and that line break. Opening.java asks the JDK’s own compiler, through javax.tools, about three ways of opening a text block, and prints each opening line with · for a space:

Three opening lines, compiled Java · Opening.java
import java.net.URI;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

// Asks the JDK's own compiler (javax.tools) whether three ways of opening a text block compile, and prints the first
// error javac reports. The class files go to the folder out/.
void main() throws Exception {
    Files.createDirectories(Path.of("out"));
    check("A", "String s = \"\"\"Namaste\"\"\";");
    check("B", "String s = \"\"\"   \n        Namaste\"\"\";");
    check("C", "String s = \"\"\" // greeting\n        Namaste\"\"\";");
}

void check(String name, String field) {
    String code = "class " + name + " {\n    " + field + "\n}\n";
    JavaFileObject source = new SimpleJavaFileObject(URI.create("string:///" + name + ".java"), JavaFileObject.Kind.SOURCE) {
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    };
    var diagnostics = new DiagnosticCollector<JavaFileObject>();
    boolean ok = ToolProvider.getSystemJavaCompiler().getTask(null, null, diagnostics, List.of("-d", "out"), null, List.of(source)).call();
    String opening = field.substring(field.indexOf("\"\"\""), field.contains("\n") ? field.indexOf('\n') : field.length());
    IO.println(opening.replace(" ", "·") + "  ->  " + (ok ? "compiles" : "error: " + diagnostics.getDiagnostics().getFirst().getMessage(Locale.ROOT)));
}

Output

"""Namaste""";  ->  error: illegal text block open delimiter sequence, missing line terminator
"""···  ->  compiles
"""·//·greeting  ->  error: illegal text block open delimiter sequence, missing line terminator

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Opening.java

"""Namaste"""; and a comment after the opening quotes are both refused with the same message, while spaces after them are fine. So a text block can never open and close on one line: for a one-line string, an ordinary literal is the right tool.

Checkpoint

Check: Opening and closing a text block

  1. Question 1 of 2 Does String s = """ // greeting, followed by a new line and Namaste""";, compile?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: No. Only spaces, tabs and form feeds may follow the opening quotes on their line

    Put the comment on the line above the text block, or after the closing quotes and the semicolon.

  2. Question 2 of 2 A source file saved on Windows ends every line with CR LF. Which line ending do the lines of a text block in that file get?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A line feed, \n, the same as everywhere

    A text block ends its lines with \n wherever it was written. To get \r\n, write the escape \r at the end of each line, or convert the finished string.

The least indented line sets the margin, closing quotes included

The spaces in front of each line serve two purposes. Some only indent the text to match the code around it, and the language calls them incidental white space: The white space in a text block that only lines it up with the code around it: the indentation all its lines share, measured from the least indented line, and spaces at the ends of lines. The compiler removes it.. Others belong to the text, such as the extra indentation of a nested HTML tag, and are essential:

A text block whose lines start with 8 spaces of indentation: the compiler removes those 8 from every line and keeps 2 extra spaces.Which spaces a text block keepsString html = """8 spaces<p>Namaste</p>8 spaces2 kept<p>Confirmed</p>8 spaces""";The margin is the indentation of the leastindented line, the closing """ line included:8 spaces here. The compiler removes them fromevery line, and the spaces at the ends of lines.incidental white space: removedessential white space: keptThe string, with · for a space, ⏎ for a line break:<p>Namaste</p>⏎··<p>Confirmed</p>⏎The line break before """ ends the last line.

Incidental and essential white space in a text block

Text description of the diagram

The diagram shows the text block String html = """ followed by three lines, each drawn as boxes.

  1. The first line is 8 spaces followed by <p>Namaste</p>.
  2. The second line is 8 spaces, then 2 more spaces, then <p>Confirmed</p>.
  3. The third line is 8 spaces followed by the closing delimiter """;.

A dashed vertical line marks the margin after the first 8 spaces of every line. The 8 spaces in front of the margin are incidental white space, shaded grey and removed by the compiler; the 2 extra spaces of the second line are essential white space, kept. The margin is the indentation of the least indented line, and the closing delimiter's line counts too. Trailing spaces at the ends of lines are removed as well.

The resulting string is two lines: <p>Namaste</p> and a line break, then 2 spaces, <p>Confirmed</p> and a line break. The line break before the closing delimiter ends the last line.

The compiler tells them apart by finding the least indented line, counting the line of the closing """ when it stands alone, and removing that many white space characters from the start of every line: that amount is the margin. The program below writes the same two lines four times and moves only the closing delimiter:

Four places for the closing delimiter Java · Delimiter.java
// The same two lines four times. Only the place of the closing """ changes, and with it the result.
// visible() shows each space as · and each line break as ⏎, so the whitespace can be counted.
void main() {
    String left = """
            <p>Namaste</p>
              <p>Your order is confirmed.</p>
        """;
    String aligned = """
            <p>Namaste</p>
              <p>Your order is confirmed.</p>
            """;
    String right = """
            <p>Namaste</p>
              <p>Your order is confirmed.</p>
                """;
    String sameLine = """
            <p>Namaste</p>
              <p>Your order is confirmed.</p>""";

    IO.println("closing delimiter 4 columns left of the text:");
    IO.println(visible(left));
    IO.println("closing delimiter under the text:");
    IO.println(visible(aligned));
    IO.println("closing delimiter 4 columns right of the text:");
    IO.println(visible(right));
    IO.println("closing delimiter at the end of the last line:");
    IO.println(visible(sameLine));
}

String visible(String s) {
    return s.replace(" ", "·").replace("\n", "⏎\n");
}

Output

closing delimiter 4 columns left of the text:
····<p>Namaste</p>⏎
······<p>Your·order·is·confirmed.</p>⏎

closing delimiter under the text:
<p>Namaste</p>⏎
··<p>Your·order·is·confirmed.</p>⏎

closing delimiter 4 columns right of the text:
<p>Namaste</p>⏎
··<p>Your·order·is·confirmed.</p>⏎

closing delimiter at the end of the last line:
<p>Namaste</p>⏎
··<p>Your·order·is·confirmed.</p>

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Delimiter.java

Step through how the compiler finds the margin of each of the four:

Interactive · step by step Where the closing quotes put the margin Step through the four text blocks of Delimiter.java. The margin is the smallest indentation the compiler counts.
Where the closing quotes put the margin, step 5 of 5. Showing line 1: <p>Namaste</p>, line 2: <p>Your order…, the closing """ (at the end of line 2), the margin (12 spaces, from line 1), what each line keeps (no line break at the end). In focus: line 2: <p>Your order…, the closing """, what each line keeps.removed from eachline 1: <p>Namaste</p>12 spaces in frontline 2: <p>Your order…14 spaces in frontthe closing """at the end of line 2the margin12 spaces, from line 1what each line keepsno line break at the end

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Count the indentation. Both content lines of Delimiter.java are indented in the source: line 1 by 12 spaces, line 2 by 14. The compiler takes the smallest indentation of these lines, and of the closing line when the quotes stand on a line of their own.
  2. Closing 4 columns left. The closing quotes stand alone, indented 8 spaces. That is now the smallest, so 8 spaces go from every line: line 1 keeps 4 spaces and line 2 keeps 6, as the run's first case printed.
  3. Closing under the text. Indented 12 spaces, the closing line ties with line 1. 12 spaces go from every line, so line 1 keeps none and line 2 keeps the 2 spaces that indent the nested tag: the usual layout.
  4. Closing further right. Indented 16 spaces, the closing line is no longer the smallest, so line 1 sets the margin again. The result is the same as with the quotes under the text, as the run's third case shows.
  5. Closing on the last line. With the quotes at the end of line 2 there is no closing line to count, and the string does not end with a line break. Line 1 sets the margin, 12 spaces, and every result line ends where its text ends.

  • Four columns left of the text: the closing line is now the least indented one, so the text keeps 4 extra spaces on every line. Moving the delimiter left is the intended way to indent a whole block.
  • Under the text: the first line and the closing line share the smallest indentation, so it all goes and the nested tag keeps its own 2 spaces. This is the usual layout.
  • Four columns right of the text: nothing changes, because the text lines are then the least indented.
  • At the end of the last line: there is no closing line to count, and the string does not end with a line break, which suits a value such as a single SQL statement or a short message.

Spaces at the end of a line are removed too: they are invisible in an editor, they often arrive with pasted text, and they would silently change the string’s length. When a trailing space matters, write it as an escape, which the next section covers.

Indentation that mixes tabs and spaces is the trap of this rule. Each tab counts as one white space character, the same as a space, so a line indented with one tab and a line indented with eight spaces have a smallest indentation of 1: one character is removed from each, and Dosa keeps 7 of its 8 spaces. TabIndent.java shows it with stripIndent(), the String method that removes incidental white space by the same rule as the compiler, then compiles the same lines as a text block with javac -Xlint:text-blocks:

A tab and eight spaces, stripped and compiled Java · TabIndent.java
import java.net.URI;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

// Indentation that mixes a tab and spaces: the first line starts with one tab, the second with eight spaces.
// stripIndent() removes incidental white space by the rule the compiler uses for text blocks, and then the JDK's own
// compiler (javax.tools) compiles the same lines as a text block with -Xlint:text-blocks. Class files go to out/.
void main() throws Exception {
    String mixed = "\tIdli\n        Dosa";
    IO.println("stripIndent():");
    IO.println(visible(mixed.stripIndent()));

    Files.createDirectories(Path.of("out"));
    String code = "class Menu {\n    String menu = \"\"\"\n" + mixed + "\"\"\";\n}\n";
    JavaFileObject source = new SimpleJavaFileObject(URI.create("string:///Menu.java"), JavaFileObject.Kind.SOURCE) {
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    };
    var diagnostics = new DiagnosticCollector<JavaFileObject>();
    boolean ok = ToolProvider.getSystemJavaCompiler()
            .getTask(null, null, diagnostics, List.of("-d", "out", "-Xlint:text-blocks"), null, List.of(source))
            .call();
    IO.println("javac -Xlint:text-blocks Menu.java: " + (ok ? "compiles" : "does not compile") + ", with "
            + diagnostics.getDiagnostics().size() + " warning");
    for (var d : diagnostics.getDiagnostics()) {
        IO.println("  Menu.java:" + d.getLineNumber() + ": " + d.getKind().toString().toLowerCase(Locale.ROOT) + ": "
                + d.getMessage(Locale.ROOT));
    }
}

String visible(String s) {
    return s.replace(" ", "·").replace("\t", "→").replace("\n", "⏎\n");
}

Output

stripIndent():
Idli⏎
·······Dosa
javac -Xlint:text-blocks Menu.java: compiles, with 1 warning
  Menu.java:2: warning: inconsistent white space indentation

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java TabIndent.java

The warning says that the margin the compiler found is probably not the one you meant. Indent text blocks with spaces only, which most editors can be set to do.

Key idea

The margin is the indentation of the least indented line, the closing quotes’ line included when they stand alone: move the quotes left to keep indentation, and put them at the end of the last line to drop the final line break.

Checkpoint

Check: The margin and the closing quotes

  1. Question 1 of 2 The two content lines of a text block are indented 12 and 14 spaces, and the closing quotes stand alone on a line indented 20 spaces. How many spaces does the first line keep?

    Type a number.

    A hint

    The closing line counts, but only if it is the least indented.

    Show the answer: Question 1 of 2

    Answer: 0 spaces

    The margin is the smallest of 12, 14 and 20, which is 12, so the first line keeps nothing and the second keeps 2. Moving the quotes to the right of the text changes nothing, as the run's third case showed.

  2. Question 2 of 2 One line of a text block is indented with 2 tabs and the next with 8 spaces, and the closing quotes end the last line. How many white space chars does the compiler remove from the start of each line?

    Type a number.

    A hint

    A tab counts as one white space char, the same as a space.

    Show the answer: Question 2 of 2

    Answer: 2 chars

    The margin is the smaller indentation, 2 chars, so the tab line loses both tabs and the other line keeps 6 of its 8 spaces. That is why javac -Xlint:text-blocks warns about inconsistent white space indentation.

Escapes are applied last, after the margin is removed

All the escapes of ordinary string literals work in text blocks, and two arrived with them in JDK 15: \s, a space that survives the removal of trailing white space (ordinary literals accept it too), and a backslash at the very end of a line, a line continuation: A backslash at the very end of a line in a text block, an escape that stands for nothing: it joins the line to the next one, so a long line of source can stay one line in the string., which joins the line to the next one and exists only in text blocks. Both appear in this form:

Escapes in a text block, and one Unicode escape Java · Escapes.java
// The escape sequences that matter most in text blocks. visible() shows each space as ·, each tab as → and each
// line break as ⏎, so the white space can be counted.
void main() {
    String form = """
            Name:\s\s
            City: Pune, \
            Maharashtra
            Says: \"""Hello\"""
            Tab:\tafter a tab
            """;
    IO.print(visible(form));
    IO.println("length " + form.length() + ", lines " + form.lines().count());

    // A Unicode escape is translated before the compiler reads the text block, so it is an ordinary space by then,
    // and a space at the end of a line is removed.
    String unicode = """
            Name:\u0020\u0020
            """;
    IO.print(visible(unicode));
}

String visible(String s) {
    return s.replace(" ", "·").replace("\t", "→").replace("\n", "⏎\n");
}

Output

Name:··⏎
City:·Pune,·Maharashtra⏎
Says:·"""Hello"""⏎
Tab:→after·a·tab⏎
length 67, lines 4
Name:⏎

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Escapes.java

  • Name:\s\s kept its two trailing spaces as ··.
  • City: Pune, \ followed by a line break became one line, City: Pune, Maharashtra: the way to break a long line in the source without breaking it in the string.
  • Three quotes in a row would close the block, so one of them needs a backslash: \""" produces """.
  • \t produced a real tab, shown as →.

The order of the compiler’s work explains every line of that output. The language specification gives three steps, and the escapes come last. Step through them on the form:

Interactive · stage by stage The three steps that turn a text block into a String Step through what the compiler does with the form of Escapes.java, in the order the language specification gives.
The three steps that turn a text block into a String, step 5 of 5. Showing the text as written, 1. line ends become \n, 2. incidental space goes (12 spaces from each line), 3. escapes are applied (2 spaces kept, 2 lines joined), the String. In focus: the String.the text as written5 lines, 12 spaces in front1. line ends become \nCR LF and CR become LF2. incidental space goes12 spaces from each line3. escapes are applied2 spaces kept, 2 lines joinedthe String4 lines, 67 chars

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. As written. The form has five source lines, each indented 12 spaces, with the closing quotes under them. Name:\s\s ends with two escapes, and City: Pune, \ ends with a backslash.
  2. Step 1, line ends. Every line terminator becomes a single line feed, \n, whatever the file was saved with. A file saved on Windows, with CR LF at the end of each line, gives the same string as one saved on Linux.
  3. Step 2, the margin. Incidental white space goes, as stripIndent removes it: the 12-space margin from every line, and spaces at the ends of lines. The escapes are still plain chars here, a backslash and an s, so nothing after them is stripped.
  4. Step 3, the escapes. Now the escapes are read: each \s becomes a space, the backslash at the end of the City line joins it to the next line, \" becomes a quote and \t a tab.
  5. The String. The result has 4 lines and 67 chars, as the run printed: Name: with its two spaces, City: Pune, Maharashtra on one line, three quotes either side of Hello, and a real tab after Tab:.

Because escapes are applied after the margin is counted and the line ends are stripped, an escaped tab or line break never changes the indentation, and \s works as a fence that keeps the spaces before it. A Unicode escape: A backslash, the letter u and four hexadecimal digits, such as \u000a, that stands for one character. The compiler replaces every one in a source file before it looks for comments, so \u000a in a comment is a real line break. More in “Comments, Javadoc and naming conventions” such as \u0020 is different: the compiler translates it before it reads the text block at all, so it is an ordinary space by then, and the run’s last line, Name:⏎, shows both of them removed as trailing white space.

String Escape / Unescape Escape text for a JSON string, or turn escapes back into text, when a value has to go inside quotes.

Checkpoint

Check: Escapes, applied last

  1. Question 1 of 2 Name:\s\s is one line of the run's form. How many chars long is that line, without its line break?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 7 chars

    Name: is 5 chars, and each \s becomes one space that is not stripped: 5 + 2 = 7. The run printed it as Name:··.

  2. Question 2 of 2 In City: Pune, \ at the end of a line, why does the space before the backslash survive?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: When trailing white space is removed, the line still ends with the backslash; the escape is read only afterwards

    The run printed City:·Pune,·Maharashtra: the space stayed, and the line break between Pune, and Maharashtra became nothing.

formatted() fills a template, unescaped and in the default locale

formatted(args) is String.format(this, args) written the other way round, so a text block with placeholders is a handy template for messages, reports and test data. The template is a format string: The text that printf, String.format and formatted fill with values: ordinary characters are copied as they are, and each % starts a format specifier, such as %d or %.2f, that is replaced by the next value. More in “Formatted output with printf and formatted”, and two cautions come with it.

First, it formats numbers and dates with the computer’s default locale: The locale the JVM takes from the computer's settings when it starts, which String.format, printf and formatted use whenever no Locale is passed. The options -Duser.language and -Duser.country set it. More in “Formatted output with printf and formatted”, so %,d prints 125,000 in one country and 125.000 in another:

One template under two default locales Java · TemplateLocale.java
// formatted() fills a template with the default locale, so a %,d grouping depends on the computer's settings.
// String.format with an explicit locale gives the same text everywhere.
void main() {
    String template = """
            Order ATF-1042: ₹%,d""";
    for (Locale where : new Locale[] {Locale.US, Locale.GERMANY}) {
        Locale.setDefault(where);
        IO.println("default " + where.toLanguageTag() + ": " + template.formatted(125000));
    }
    IO.println("Locale.ROOT, any default: " + String.format(Locale.ROOT, template, 125000));
}

Output

default en-US: Order ATF-1042: ₹125,000
default de-DE: Order ATF-1042: ₹125.000
Locale.ROOT, any default: Order ATF-1042: ₹125,000

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java TemplateLocale.java

When the output must be the same everywhere, call String.format(Locale.ROOT, template, args), which takes the locale explicitly, as the run’s last line does.

Second, and more serious, it inserts values exactly as they are:

A JSON template and a name with quotes in it Java · JsonBreak.java
// A JSON request body built with formatted(). It is correct until a value contains a double quote.
void main() {
    String body = """
            {"name": "%s", "city": "%s"}""";
    IO.println(body.formatted("Asha Rao", "Pune"));
    IO.println(body.formatted("Asha \"Ash\" Rao", "Pune"));
}

Output

{"name": "Asha Rao", "city": "Pune"}
{"name": "Asha "Ash" Rao", "city": "Pune"}

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java JsonBreak.java

The second line is not valid JSON. Step through why, from the template to the point where a JSON reader gives up:

Interactive · stage by stage A quote in a value breaks the JSON Step through the second call of JsonBreak.java, from the template to the point where a JSON reader gives up.
A quote in a value breaks the JSON, step 5 of 5. Showing the template, the value, formatted() pastes it in, a JSON reader (the string is "Asha "), not valid JSON. In focus: not valid JSON.the template{"name": "%s", …}the valueAsha "Ash" Raoformatted() pastes it inunchangeda JSON readerthe string is "Asha "not valid JSONAsh where a comma belongs

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The template. The text block holds one line, {"name": "%s", "city": "%s"}. formatted() replaces each %s with the text of the next value, exactly as String.format would.
  2. The value. The first value is the name Asha "Ash" Rao. It holds two double quotes, which mean something to JSON: a string begins and ends with a quotation mark.
  3. formatted() pastes it in. formatted() knows nothing about JSON, so the name goes in as it is. The line becomes {"name": "Asha "Ash" Rao", "city": "Pune"}, the second line the run printed.
  4. The string ends early. A JSON reader starts the name's string at its first quote and ends it at the next one, after Asha and a space. In JSON a quote inside a string must be written with a backslash, as \".
  5. The reader gives up. After a value inside an object, JSON allows only a comma or a closing brace. The reader finds Ash instead, so the whole document is rejected, although the template was right.

The same happens to HTML when a value contains < or &, and to SQL when a value contains a quote, where it becomes a security hole called A flaw in which text from outside a program becomes part of an SQL statement, so a crafted value can change what the statement does. Binding the value with a PreparedStatement keeps it data..

Templates are for text you wrote

Never build SQL, HTML or JSON by formatting values you did not write yourself: use a PreparedStatement for SQL (the JDBC lessons in the back-end module show how), escape values for HTML, and let a JSON library write JSON.

JSON Validator Paste the second line JsonBreak.java printed to see exactly where the JSON breaks.

Checkpoint

Check: Templates with formatted()

  1. Question 1 of 2 formatted(name) puts name into {"name": "%s"}. Which name makes the result invalid JSON?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Asha "Ash" Rao

    formatted() inserts values as they are. A JSON library escapes the quote as \" and keeps the document valid.

  2. Question 2 of 2 On a computer whose default locale is de-DE, what does "%,d".formatted(125000) give?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 125.000

    Pass the locale yourself, String.format(Locale.ROOT, template, value), when the text must be the same on every computer.

Practise: a bill that matches to the last space

Bill.java holds the method to write and two checks that compare its output with the expected bill exactly, spaces and line breaks included. Each item line is the name padded to 16 characters, an x and the quantity padded to 3, and the amount right-aligned in 5 characters. As it starts, bill returns an empty string, and the checks show the expected text with · for each space:

The starting point Java · Bill.java
// Your turn: write bill() so that it returns exactly the text the checks expect, every space and line break included.
// Each item line is the name padded to 16 characters, "x" and the quantity padded to 3, and the amount (quantity times
// price) right-aligned in 5 characters. A text block makes a good template for the whole bill.
String bill(int table, String[] names, int[] quantities, int[] prices) {
    return "";   // Write your code here.
}

int failed = 0;

void check(String got, String expected) {
    if (!got.equals(expected)) {
        failed++;
        IO.println("FAIL: the bill differs. Expected, with · for each space and ⏎ for each line break:");
        IO.print(visible(expected));
        IO.println("got:");
        IO.print(visible(got));
    }
}

String visible(String s) {
    return s.isEmpty() ? "(nothing)\n" : s.replace(" ", "·").replace("\n", "⏎\n") + (s.endsWith("\n") ? "" : "\n");
}

void main() {
    check(bill(4, new String[] {"Masala dosa", "Filter coffee"}, new int[] {2, 2}, new int[] {90, 30}), """
            Canteen bill, table 4
            ---------------------
            Masala dosa     x2   ₹180
            Filter coffee   x2    ₹60
            Total                ₹240
            """);
    check(bill(7, new String[] {"Idli", "Vada", "Lemon rice"}, new int[] {3, 2, 1}, new int[] {20, 25, 70}), """
            Canteen bill, table 7
            ---------------------
            Idli            x3    ₹60
            Vada            x2    ₹50
            Lemon rice      x1    ₹70
            Total                ₹180
            """);
    IO.println(failed + " of 2 checks failed");
    if (failed > 0) {
        System.exit(1);
    }
}

Output (exit status 1)

FAIL: the bill differs. Expected, with · for each space and ⏎ for each line break:
Canteen·bill,·table·4⏎
---------------------⏎
Masala·dosa·····x2···₹180⏎
Filter·coffee···x2····₹60⏎
Total················₹240⏎
got:
(nothing)
FAIL: the bill differs. Expected, with · for each space and ⏎ for each line break:
Canteen·bill,·table·7⏎
---------------------⏎
Idli············x3····₹60⏎
Vada············x2····₹50⏎
Lemon·rice······x1····₹70⏎
Total················₹180⏎
got:
(nothing)
2 of 2 checks failed

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Bill.java

You are done when it prints 0 of 2 checks failed. Hints: make the bill a text block with three placeholders, the table number, the item lines and the total, and end the line of the item placeholder with a backslash, so that Total follows the item lines directly. Build each item line with "%-16sx%-3d%5s\n".formatted(...), passing "₹" + amount as the last value. Use \n rather than %n, which is \r\n on Windows and would make the bill differ there.

Then run through the four ideas, from the opening quotes to formatted(): the first question is on the idea you know least, and every choice carries its own reason.

Practice that adapts to you

Practise this lesson

10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 Which of these lines does not compile?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: String s = """Namaste""";

    The opening delimiter of a text block is three double quotes followed by a line break (spaces and tabs may come before it), so a text block cannot open and close on one line. The second line is an ordinary string literal that holds three quote characters on each side of Namaste.

  2. Question 2 of 10 What does this print?

    Read the code, then choose one answer.

    String a = """
            Pune""";
    IO.println(a.equals("Pune"));
    Show the answer: Question 2 of 10

    Answer: true

    A text block is an ordinary String constant. Here the content is one line, Pune, and nothing else.

  3. Question 3 of 10 What string does s hold? Each line of content is indented by 8 spaces and the closing delimiter by 6. In the choices, · marks a space and ⏎ a line break.

    Read the code, then choose one answer.

    String s = """
            idli
            dosa
          """;
    Show the answer: Question 3 of 10

    Answer: ··idli⏎··dosa⏎

    The closing delimiter's line is the least indented, with 6 spaces, so 6 are removed from every line and each line keeps 2. The delimiter stands on its own line, so the last line ends with a line break.

  4. Question 4 of 10 Which white space at the start of a text block's lines does the compiler remove?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: The indentation that all lines share, counting the closing line when it stands alone

    The margin is the indentation of the least indented line. Moving the closing quotes to the left of the text is the way to keep more indentation.

  5. Question 5 of 10 In TabIndent.java, one line starts with a tab and the next with 8 spaces. How many spaces does the second line keep after stripIndent()?

    Type a number.

    Show the answer: Question 5 of 10

    Answer: 7 spaces

    The tab and each space count as one white space char, so the margin is 1. One char goes from each line, and Dosa keeps 7 of its 8 spaces, as the run printed ·······Dosa.

  6. Question 6 of 10 What is s.length()?

    Read the code, then choose one answer.

    String s = """
        a\s\s
        """;
    Show the answer: Question 6 of 10

    Answer: 4

    Four spaces of incidental indentation are removed from both lines, which leaves a, the two escapes and a line break. Escapes are interpreted last, so each \s becomes one space that is not removed as trailing white space: "a", 2 spaces and "\n" make 4 characters.

  7. Question 7 of 10 What does s contain? In the choices, · marks a space and ⏎ a line break.

    Read the code, then choose one answer.

    String s = """
        Pune, \
        Maharashtra""";
    Show the answer: Question 7 of 10

    Answer: Pune,·Maharashtra

    A backslash at the end of a line joins it to the next line, so no line break is inserted. The space before the backslash stays: when trailing white space is removed, the line still ends with the backslash, and the escape is interpreted only afterwards. The closing delimiter on the last line means the string has no final line break.

  8. Question 8 of 10 How do you put three double quotes in a row inside a text block?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: Put a backslash before one of them, as in \"""

    A single quote or two quotes in a row need no escape at all inside a text block. Only a run of three needs one backslash.

  9. Question 9 of 10 A text block holds the line {"name": "%s"} and is filled in with formatted(name). Why is that risky when the name comes from a user?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: A double quote in the name ends the JSON string early and breaks the document

    formatted() inserts the value exactly as it is. The lesson's JsonBreak.java printed {"name": "Asha "Ash" Rao", …}, which is not valid JSON. Let a JSON library write JSON, escape values for HTML, and bind SQL values with a PreparedStatement.

  10. Question 10 of 10 What is template.formatted(a, b) the same as?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: String.format(template, a, b)

    Because it uses the default locale, numbers and dates in a formatted() template can look different on another computer. String.format with an explicit Locale avoids that.

Interview questions

Fresher · What is incidental white space in a text block, and how does the closing delimiter affect it?

Incidental white space is the indentation that only lines a text block up with the surrounding code, as opposed to essential white space, which belongs to the text, such as the extra indentation of a nested tag. The compiler finds the smallest indentation among the non-blank lines of the content and the line of the closing delimiter, if that stands on a line of its own, and removes that many white space characters from the start of every line; it also removes trailing white space from each line. So the closing """ matters: placed under the text, it changes nothing; moved four columns to the left, it leaves four extra spaces on every line; placed at the end of the last line, it ends the string without a line break. A good answer mentions \s for a trailing space that must stay.

Expect the interviewer to follow up with code to work out by hand. Here are two such questions, with the reasoning after each:

Checkpoint

Interview round: two follow-up questions

  1. Question 1 of 2 An interviewer writes this text block and asks which ordinary string literal it equals. Which one?

    Read the code, then choose one answer.

    String json = """
        {
          "id": 7
        }
        """;
    Show the answer: Question 1 of 2

    Answer: "{\n \"id\": 7\n}\n"

    Work it out as the compiler does: find the margin from the least indented line, closing line included, remove it, then add a line break wherever a source line ended.

  2. Question 2 of 2 The interviewer asks why the compiler interprets escapes only after it removes incidental white space. Which answer is strongest?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: So that an escape such as \s or \t can never change how the margin is counted or be stripped as trailing white space

    JLS 3.10.6 gives the order: line terminators, then incidental white space, then escapes. That order is what lets \s act as a fence that keeps trailing spaces.

Two questions in the module bank need a longer answer: a search box that fills an SQL template, and a report test that fails on one build computer only.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    In a code review you find a text block holding SELECT name, fare FROM trains WHERE city = '%s', filled in with formatted(city), where city comes from a search box. The tests pass. What do you say, and how would you rewrite it while keeping the text block?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A test compares a generated report with an expected text block and fails only on the Windows build agent, although the two texts look identical in the failure message. What do you check, in what order, and how do you make the test reliable without hiding real differences?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A text block opens with """ and a line break, ends with """, and is an ordinary String constant; only white space may follow the opening quotes on their line.
  • The compiler normalises line endings to \n, then removes the incidental indentation measured from the least indented line, the closing delimiter’s line included, and the spaces at the ends of lines.
  • Move the closing delimiter left to keep indentation, and put it at the end of the last line to drop the final line break; a tab counts as one space, so mixed indentation strips too little.
  • \s keeps a space, a backslash at the end of a line joins it to the next, and \""" writes three quotes, because escapes are applied after the indentation is removed.
  • formatted() uses the default locale and inserts values unescaped: never build SQL, HTML or JSON from outside values with it.

Terms in this lesson

incidental white space
The white space in a text block that only lines it up with the code around it: the indentation all its lines share, measured from the least indented line, and spaces at the ends of lines. The compiler removes it.
line continuation
A backslash at the very end of a line in a text block, an escape that stands for nothing: it joins the line to the next one, so a long line of source can stay one line in the string.
text block
A Java string literal that spans several lines: three double quotes and a line break open it, three double quotes close it, and the text between keeps its quotes and line breaks without escapes. Standard since JDK 15.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.