Command Palette

Search for a command to run...

PHASE 6Intermediate Java 15+ ~29 min· topic 5 of 8

Topic 6.5

Text Blocks

In one line

A text block is a multi-line String literal written between triple quotes ("""). It keeps your line breaks, removes the indentation that only exists to line the code up, and needs almost no escaping, which makes JSON, SQL, HTML and test data readable in Java code.

Think of it like this

Pasting a poem into a letter. With old-style Strings you'd have to retype the poem as one long line, adding a special mark wherever a line should break and a backslash before every quotation mark. A text block is like taping the printed poem straight onto the page: the lines, spaces and quotes stay as they are. Java only trims off the empty margin you added to line the poem up with the rest of the letter.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Text block
A String literal that can span several lines, written between """ delimiters.
Delimiter
The marker that opens or closes something. For a text block, it's three double quotes.
Incidental whitespace
Indentation that's only there to line the text up with your code. Java removes it.
Essential whitespace
Indentation that's part of the content, such as nesting in JSON. Java keeps it.
Line terminator
The invisible character(s) that end a line: \n on Linux and macOS, \r\n on Windows.
Escape sequence
A backslash plus a character that stands for something hard to type, like \n for a new line or \s for a space.
Preview feature
A finished-but-not-final language feature you can try with --enable-preview before it becomes standard.

Step by step

01The problem: multi-line text in old Java

Before Java 15, putting a small JSON document in code meant one long line full of \n and \", or a chain of + joins. Both are hard to read and easy to break: miss one \n and two lines run together.

With a text block, the code looks like the text it produces. You can copy JSON, SQL or HTML in and out without editing every line.

Main.javawhole filejava
// Before Java 15
String old = "{\n" +
             "  \"name\": \"Asha\"\n" +
             "}\n";

// Java 15+
String json = """
    {
      "name": "Asha"
    }
    """;

02How the compiler processes a text block

javac does three steps, in this order. 1. It normalises every line terminator to \n. 2. It removes incidental whitespace: the common leading indentation, and trailing spaces on every line. 3. It interprets escape sequences such as \n, \t, \s and \<line-end>.

The order matters. Because escapes are processed last, a \n you type creates a line break that indentation stripping never sees, and \s survives the trailing-space removal. The final String is stored in the class file exactly like a normal literal, so there's zero cost at run time.

How the compiler processes a text blockdiagram
Rendering diagram…

03Finding the incidental indentation

Count the leading whitespace on every non-blank line between the delimiters, plus the line with the closing """. The smallest count is the incidental indentation; remove that many characters from the start of each line.

In the example below the content lines have 4, 6 and 4 spaces and the closing line has 4, so 4 are removed: green keeps 2 spaces of essential indentation.

Blank lines don't count towards the minimum (they'd always win with 0), but they stay in the String as empty lines.

Main.javawhole filejava
String colours = """
    red
      green
    blue
    """;
// "red\n  green\nblue\n"

04Steering with the closing delimiter

Put the closing """ further left than the content and it becomes the smallest indentation, so the content keeps the difference as real spaces. That's how you produce indented output on purpose.

Put the closing """ at the end of the last content line and the String has no final newline. Use that when the text is going into println, which adds its own newline, or into a comparison.

05Escapes you'll use in text blocks

\s is one space that's never stripped: kept\s keeps a trailing space. A \ as the last character on a line removes the line break, joining the next line on; the next line's indentation is still stripped normally.

Quotes rarely need escaping. Inside a text block "hi" is fine. Only three in a row would close the block early, so write \""" to put """ in the content.

A backslash meant literally (a Windows path, a regex) still needs doubling: C:\\temp. Text blocks are not "raw strings"; Java still has no raw string literal.

06Filling in values: formatted()

Text blocks are constants, so there's no ${name} syntax inside them. Use """...""".formatted(a, b) (Java 15), which works like String.format with the block as the format string (Topic 6.6).

For SQL, use this only for fixed parts like table names you control. Values from users must go through PreparedStatement parameters (?), never formatting: building SQL from user text is how SQL injection happens.

Main.javawhole filejava
String html = """
    <p class="greeting">
      Hello, %s!
    </p>
    """.formatted(name);

07Tabs vs spaces: a quiet trap

The compiler counts whitespace characters, not visual columns. A tab counts as one character, the same as a space. If some lines are indented with tabs and others with spaces, the minimum is computed on characters and you get surprising indentation.

Compile with -Xlint:text-blocks and javac warns you. Better still, set your editor to indent with spaces only.

terminal
$ javac -Xlint:text-blocks Main.java
── expected output ──
Main.java:3: warning: [text-blocks] inconsistent white space indentation
String s = """
^
Main.java:3: warning: [text-blocks] trailing white space will be removed
String s = """
^
2 warnings

Try it yourself

  1. 1

    Move the closing delimiter

    In the indentation example, move b's closing """ two more spaces to the left. Predict the new visible(b) (how many dots before red?), then run.

  2. 2

    Drop the trailing newline

    In the JSON example, put the closing """ right after the final }. Predict the ends with a newline, length and equals classic literal lines, then run and fix classic so they're equal again.

  3. 3

    Write a menu

    Write a text block that prints a three-line menu with prices, using formatted to fill in the prices from three double variables with %.2f. Predict the exact output first.

Code & diagrams

A JSON text block is just a String Java 15+ New tab

Both are compile-time constants with identical contents, so they are the same interned object. Never rely on == for real comparisons.

Sign in to run this example in your browser.

Expected output

{
  "name": "Asha",
  "city": "Pune"
}
equals classic literal: true
same pooled object: true
lines: 4
ends with a newline: true
length: 39
Indentation, closing delimiter and escapes Java 15+ New tab

visible() shows each space as a dot and each newline as \n so you can see exactly what the compiler kept.

Sign in to run this example in your browser.

Expected output

a: [red\n..green\n]
b: [..red\n....green\n]
c: [red\ngreen]
d: [kept.\none.long.line.continues.here\n]
e: She said "hi" and """left"""
formatted(), stripIndent() and translateEscapes() Java 15+ New tab

formatted() is fine for this demo; real queries must pass user values as PreparedStatement parameters, not by formatting.

Sign in to run this example in your browser.

Expected output

SELECT id, name
FROM users
WHERE city = 'Pune'
  AND age > 18
alpha
  beta
gamma
before: line one\nline two
after translateEscapes:
line one
line two
Text blocks in tests and HTML (fragment)java
// Expected output in a unit test reads like the real thing
String expected = """
    Name   Qty
    tea      2
    samosa   3
    """;
assertEquals(expected, report.render());

// Inline HTML for an email template
String body = """
    <html>
      <body>
        <h1>Order %s confirmed</h1>
      </body>
    </html>
    """.formatted(orderId);

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put text on the opening line

Write String s = """hello"""; on one line.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: illegal text block open delimiter sequence, missing line terminator
String s = """hello""";
^
Main.java:3: error: illegal text block open delimiter sequence, missing line terminator
String s = """hello""";
^
2 errors

Break #2

Compile for an old Java version

Compile a file containing a text block with javac --release 14 Main.java.

terminal
$ javac --release 14 Main.java
── what you'll see ──
Main.java:3: error: text blocks are not supported in -source 14
String s = """
^
(use -source 15 or higher to enable text blocks)
1 error

Break #3

Mix tabs and spaces

Indent one line of a text block with a tab and the others with spaces.

terminal
$ javac -Xlint:text-blocks Main.java && java Main
── what you'll see ──
Main.java:3: warning: [text-blocks] inconsistent white space indentation
String s = """
^
Main.java:3: warning: [text-blocks] trailing white space will be removed
String s = """
^
2 warnings
one
two

Myth vs fact

Myth

Text blocks are raw strings, so backslashes don't need escaping.

Fact

Escapes are still processed. \n is a newline and a literal backslash must be written \\. Java has no raw string literal (a raw-string proposal, JEP 326, was withdrawn).

Myth

The indentation in my source ends up in the String.

Fact

Only essential indentation does. The common indentation of the content and closing lines is removed, and trailing spaces are stripped.

Myth

Text blocks are slower or create a different kind of object.

Fact

They compile to an ordinary String constant in the class file. At run time there is no difference from a normal literal, and equal text blocks and literals are the same interned object.

Myth

Text blocks support string interpolation like ${name}.

Fact

They don't. Use formatted(...) or String.format. (String templates were previewed in Java 21 and 22 and then withdrawn.)

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The processing is specified in JLS §3.10.6 and exposed as String.stripIndent() and String.translateEscapes(). Note that translateEscapes does not handle \uXXXX: Unicode escapes are translated much earlier by the compiler, before tokenising, in every part of the source.

  • ▸

    Because text blocks are constant expressions, they can be used in annotations (@Query("""...""") in Spring Data) and as case labels, and they are folded with other constants by javac.

  • ▸

    Line endings are normalised to LF at compile time. If a protocol needs CRLF (HTTP headers, some email formats), add \r explicitly or call .replace("\n", "\r\n"); don't rely on the source file's line endings, which Git may rewrite on checkout.

  • ▸

    String templates (JEP 430) were a preview in Java 21 and 22 and were removed in Java 23 pending a redesign. Code written against them won't compile on newer JDKs, which is a good reason to avoid preview features in production code.

Remember this

  1. 1

    A text block starts with three double quotes followed by a line break, and ends with three double quotes. Everything in between is the content. """ followed by text on the same line is a compile error, because the opening line must end right after the delimiter. The result is an ordinary String: same class, same methods, and, like any literal, a compile-time constant that is interned in the string pool (Topic 6.1).

  2. 2

    Text blocks were previewed in Java 13 and 14 and became standard in Java 15 (JEP 378). Compiling one with --release 14 or older fails with text blocks are not supported in -source 14.

  3. 3

    Incidental whitespace is removed. The compiler finds the smallest indentation among all non-blank content lines and the closing delimiter's line, and removes that many leading whitespace characters from every line. The indentation that remains is essential whitespace, which you keep. So you can indent a text block to match your code without that indentation ending up in the String. Trailing spaces at the end of each line are also removed.

  4. 4

    The closing delimiter controls two things. If it sits on its own line, the String ends with a newline; if it sits right after the last word (...end"""), there's no trailing newline. And because its line counts towards the minimum indentation, moving it to the left of the content adds indentation to every line.

  5. 5

    Very little needs escaping. Double quotes are fine on their own ("name": "Asha"); only a run of three needs one escaped (\"""). All the usual escapes still work (\n, \t, \\). Java 15 also added two escapes mainly for text blocks: \s is a single space that is never stripped (useful to keep trailing spaces), and a backslash at the very end of a line (\<line-end>) joins the next line on without a newline, for splitting a long line in code.

  6. 6

    All line endings in a text block are normalised to \n, whatever the source file uses (Windows \r\n included), so the String is the same on every platform. Text blocks have no built-in variables; to fill in values use formatted(...) (Java 15, Topic 6.6) or String.format. Java 15 also added stripIndent() and translateEscapes(), which expose the same processing for Strings built at run time.

Explain it without notes

01

What is a text block and what rules apply to its opening and closing delimiters?

02

How does the compiler decide how much indentation to remove?

03

In which order does javac process a text block, and why does the order matter?

04

What do the \s and \<line-end> escapes do?

05

How do you put variable values into a text block?

Practice

01

Use a text block to print a small receipt with three items and a total, with the item names left-aligned. The output must have no leading spaces.

02

Write a text block whose output lines are indented by exactly 4 spaces, without typing 4 extra spaces on each content line. Show it with visible()-style dots.

03

Rewrite "Dear " + name + ",\n\nYour order " + id + " has shipped.\n" as a text block with formatted. Print it for Ravi and A-42.

Trade-offs

  • ↔

    Text blocks make embedded JSON, SQL and HTML readable, but large templates still belong in resource files, where non-developers can edit them and you don't need to recompile.

  • ↔

    Indentation rules are clever but invisible: a misplaced closing delimiter or a stray tab changes the String silently. Unit tests that compare exact output catch this; -Xlint:text-blocks helps.

  • ↔

    formatted is convenient for filling values, but it's slower than concatenation and its format string is checked only at run time. For hot paths or user-supplied values, build text explicitly or use a proper templating or query API.

Done when you can

  • Done when you can write a text block and say exactly which String it produces.

  • Done when you can compute the incidental indentation, including the closing delimiter's line.

  • Done when you control the trailing newline and extra indentation with the closing delimiter.

  • Done when you can use \s, \<line-end> and \""" correctly.

  • Done when you fill values with formatted and keep user values out of SQL.