Text blocks are multi-line string literals that landed in Java 15 after two preview rounds. They were designed for one specific pain point: writing readable SQL queries, HTML fragments, JSON payloads, and email templates inside Java source. This lesson covers the syntax, the "incidental whitespace" rule that decides what indentation gets stripped, the new escape sequences \ and \s, and the cases where a plain string is still appropriate.
Before Java 15, writing a multi-line string meant gluing pieces together with + and \n. The result looked like this:
Count the noise. Every line has a leading ", a trailing \n", and a +. Every double quote inside the HTML had to be escaped as \". The original HTML structure is still visible, but you've to work for it. Now consider a 40-line HTML email template or a SQL query with five joins. Code review becomes painful, and copy-pasting between Java and a .sql or .html file means rewriting the quotes by hand.
Text blocks make the same string look like the thing it actually is:
Same output, no escape characters, no concatenation, no \n markers. The HTML reads as HTML. That's the whole point of text blocks.
The journey is from a noisy concatenation to a clean literal, and both end at the exact same String. Text blocks aren't a new type. They're a friendlier way to write something String already supports.
A text block opens with three double quotes followed by a line terminator, then any number of content lines, and closes with three more double quotes. The opening """ must be followed by a newline. You can't put content on the same line as the opening delimiter; that's a compile error.
The trailing blank line matters. The closing """ sits on its own line after a final newline in the content, so the resulting string ends with \n. To avoid that trailing newline, put the closing """ immediately after the last character:
No blank line between "products." and "---", because the string itself doesn't end in \n. The position of the closing delimiter is something to think about every time you write a text block.
When the compiler sees a text block, it doesn't include every leading space verbatim. It strips a common amount of indentation, called incidental whitespace, so the string isn't polluted by the surrounding Java indentation.
The algorithm has three steps:
""" sits.The closing """ position is the most common knob to turn, because it usually wins as the leftmost reference point. Look at this example carefully:
The four content lines and the closing """ line all start with 16 spaces (4 levels of Java indentation). 16 is the minimum, so 16 spaces are stripped from every line. The <div> and </div> lines had exactly 16 spaces and end up at the left margin. The <p> line had 18 spaces (16 + 2 for HTML indentation), so it keeps 2 spaces of indentation. That's what we want.
The diagram shows the pipeline: take the lines, find the minimum indent across all of them (including the closing-delimiter line), strip that many leading characters from every line. The HTML's own indentation, which is more than the minimum, survives. The Java code's indentation, which equals the minimum, gets stripped away.
This is where the bug commonly hides. What happens when the closing """ moves left:
The closing """ is at column 9 (8 spaces of leading whitespace), and the content lines are at column 17 (16 spaces). The minimum is now 8, not 16. So only 8 spaces get stripped from each line, and every content line ends up with 8 extra spaces of leading whitespace. The <div> line ends up at column 9 in the output, which is rarely the desired result.
The fix is to align the closing """ with the leftmost content line. That way the closing-delimiter line contributes the same indent as your content, and the stripping produces clean output.
A third example where the closing delimiter sits even further left than the content:
The content lines (Coffee, Tea, Juice) each start with 18 spaces. The closing-delimiter line starts with 16 spaces. The minimum is 16. After stripping, each item keeps 2 leading spaces. The closing-delimiter line acted as the "left margin reference," and it's the typical way to control how much indentation appears in the final string.
Incidental whitespace stripping happens at compile time, not at runtime. Text blocks compile to ordinary string constants in the class file, so there's zero runtime overhead compared to a plain string literal.
\nThis is a small but important detail. No matter what platform you compile on (Windows with CRLF line endings, Linux with LF, or anything in between), the line terminators inside a text block are normalized to a single \n (LF). If you save the source file with Windows-style \r\n line endings, the compiler still produces \n in the resulting String.
Only [LF] shows up, never [CR]. If you need a \r\n sequence for, say, an HTTP wire protocol, you've to insert it explicitly with a \r escape (or call .replace("\n", "\r\n") after building the string). The normalization is a feature: it means a text block reads the same way on every developer's machine.
All the regular string escape sequences still work: \t for a tab, \n for a newline, \\ for a backslash, \" for a double quote, and A for a Unicode code point. Two additional escapes were introduced specifically for text blocks.
\ (Line Continuation)A backslash at the end of a content line suppresses the line terminator that would normally appear there. It's useful for long lines you want to wrap across multiple source lines without inserting an actual \n into the string.
The string is a single line, even though the source code spans two. The \ says "don't put a newline here." This is the multi-line equivalent of a soft wrap. The space between "orders" and "over" comes from the space character right before the \.
\s (Trailing Space Preservation)Trailing whitespace at the end of a line is normally stripped from each line of a text block, after the leading whitespace is stripped. That's usually the desired behavior, since trailing spaces are rarely intentional. Sometimes they're needed, for things like fixed-width text formatting. The \s escape produces a single space that survives trailing-whitespace stripping.
Each \s keeps one trailing space on its line. Without \s, those spaces would be stripped, and the lines would end at the last visible character. This is rarely needed in everyday code, but for table formatting, mail-merge templates, or fixed-width record output, it's handy.
Inside a text block, a single " or a pair "" is fine without any escaping. Only the three-in-a-row sequence """ is special, because that's the closing delimiter. If you need three consecutive double quotes inside the content, escape one of them as \".
The single " characters around "The shoes..." needed no escaping. The pair "" around "excellent" also needed no escaping. Only the triple sequence had to be broken up with a \" so the parser doesn't think it's seeing the closing delimiter early. That's a far smaller escaping burden than the old style of double-quoting every quote.
A text block isn't a new type. It's a different syntax for writing a String. After compilation, the result is indistinguishable from a normal string literal. Every String method works on it, and you can mix text-block strings and regular strings freely.
getClass().getName() confirms it's a plain java.lang.String. The lines() method (added in Java 11) gives you a Stream<String> of the lines, which pairs well with text blocks for processing line-by-line. There's no TextBlock class anywhere in the JDK.
Two text blocks with the same content are also identical in the string pool, the same way two regular string literals would be:
Both a and b reference the exact same interned String in the constant pool. The text block is a compile-time literal just like any other.
String.formatted() for InterpolationJava 15 also added String.formatted() (an instance method) and String.format() already existed. Together they're the standard way to inject runtime values into a text block. The placeholders use the same %s, %d, %.2f syntax as printf.
"...".formatted(args) is the same as String.format("...", args), just written as a chained method call. It reads more naturally at the end of a text block, especially when the template itself is multi-line. The arguments fill in %d, %s, %.2f in order.
A SQL query for a customer's cart items shows the same pattern:
Don't use string interpolation like this to build real SQL queries in production. It exposes the app to SQL injection if any value comes from user input. Use PreparedStatement parameters for queries against a live database. formatted() is fine for displaying queries, logging templates, or generating non-SQL strings.
A JSON product example with mixed types:
The placeholders neatly correspond to the runtime values you want to substitute. If you need real string interpolation in pure Java, note that this is the current idiom. String Templates were proposed for Java 21 but were withdrawn for redesign, so formatted() and concatenation are still the way to interpolate.
A common bug:
Every line of the email body has an unexpected two-space indent at the start, and there's a stray " " before the closing ]. The author wanted the email to be left-aligned. What went wrong?
The closing """ is two characters further to the right than the content lines. The content lines have 16 spaces, the closing """ line has 18 spaces. The minimum across all lines is 16 (the content lines), so 16 spaces get stripped. Because the closing-delimiter line contributed 18, the trailing line of the string (the empty line where the closing """ sits) starts with 2 spaces.
The more common version of this bug is the reverse, where the closing """ is too far left. Either way, the fix is to align the closing delimiter with the leftmost content line.
Fix:
Now the closing """ is aligned with the content lines (all at 16 spaces). The minimum is 16, all 16 spaces get stripped, and the content sits flush at the left margin. Whenever a text block prints with mystery indentation, the first thing to check is the column of the closing """.
Text blocks work well for multi-line content. They're more clutter than help in a few situations:
String name = "Aanya"; is shorter and clearer than the text-block equivalent. Don't use """...""" to set a one-line greeting." exact text". The rules around incidental whitespace are predictable but they're one more thing to think about."https://store.example.com/products/42" fits comfortably; using a text block would add noise.A more interesting case: an email template with conditional sections. If you find yourself building parts of a text block conditionally with if statements and re-concatenating, a templating library is probably the better choice. Text blocks are for static templates with simple %s-style placeholders. Anything more elaborate, and you're reinventing a template engine.
Putting most of the pieces together, an order-confirmation email assembled from runtime values:
The two-space indent on the line-item rows comes through because those lines have more leading whitespace than the closing """ reference line. The empty lines in the source produce empty lines in the output. The whole template reads in source form roughly the way it reads in the email body, which is exactly the kind of readability text blocks were built for.
10 quizzes