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:
\non Linux and macOS,\r\non Windows. - Escape sequence
- A backslash plus a character that stands for something hard to type, like
\nfor a new line or\sfor a space. - Preview feature
- A finished-but-not-final language feature you can try with
--enable-previewbefore 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.
// 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.
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.
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.
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.
Try it yourself
- 1
Move the closing delimiter
In the indentation example, move
b's closing"""two more spaces to the left. Predict the newvisible(b)(how many dots beforered?), then run. - 2
Drop the trailing newline
In the JSON example, put the closing
"""right after the final}. Predict theends with a newline,lengthandequals classic literallines, then run and fixclassicso they're equal again. - 3
Write a menu
Write a text block that prints a three-line menu with prices, using
formattedto fill in the prices from threedoublevariables with%.2f. Predict the exact output first.
Code & diagrams
Both are compile-time constants with identical contents, so they are the same interned object. Never rely on == for real comparisons.
Expected output
{
"name": "Asha",
"city": "Pune"
}
equals classic literal: true
same pooled object: true
lines: 4
ends with a newline: true
length: 39visible() shows each space as a dot and each newline as \n so you can see exactly what the compiler kept.
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() is fine for this demo; real queries must pass user values as PreparedStatement parameters, not by formatting.
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// 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.
Break #2
Compile for an old Java version
Compile a file containing a text block with javac --release 14 Main.java.
Break #3
Mix tabs and spaces
Indent one line of a text block with a tab and the others with spaces.
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()andString.translateEscapes(). Note thattranslateEscapesdoes 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 ascaselabels, and they are folded with other constants byjavac. - ▸
Line endings are normalised to LF at compile time. If a protocol needs CRLF (HTTP headers, some email formats), add
\rexplicitly 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
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 ordinaryString: same class, same methods, and, like any literal, a compile-time constant that is interned in the string pool (Topic 6.1). - 2
Text blocks were previewed in Java 13 and 14 and became standard in Java 15 (JEP 378). Compiling one with
--release 14or older fails withtext blocks are not supported in -source 14. - 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
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
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:\sis 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
All line endings in a text block are normalised to
\n, whatever the source file uses (Windows\r\nincluded), so the String is the same on every platform. Text blocks have no built-in variables; to fill in values useformatted(...)(Java 15, Topic 6.6) orString.format. Java 15 also addedstripIndent()andtranslateEscapes(), which expose the same processing for Strings built at run time.
Explain it without notes
What is a text block and what rules apply to its opening and closing delimiters?
How does the compiler decide how much indentation to remove?
In which order does javac process a text block, and why does the order matter?
What do the \s and \<line-end> escapes do?
How do you put variable values into a text block?
Practice
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.
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.
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-blockshelps. - ↔
formattedis 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
formattedand keep user values out of SQL.