Java Text Blocks
Text Blocks
Text blocks, finalized in Java 15 (JEP 378) after two rounds as a preview feature, give Java a multi-line string literal delimited by """. A block of JSON, SQL, or HTML embedded directly in source no longer needs + concatenation or a \n escape at the end of every line — the text is written exactly as it should read.
What Is a Text Block?
A text block is a multi-line string literal that starts and ends with """, where the content between the delimiters is written exactly as it should look, rather than escaped and concatenated line by line. JEP 378's design goal was to make embedding a genuinely multi-line piece of text — JSON, SQL, HTML — as readable in source as it is in the file it came from, while the compiler handles indentation stripping automatically.
Why Text Blocks Were Introduced
Embedding a small JSON payload as a regular string literal meant escaping every quote and manually inserting a \n at the end of each line.
// File: BeforeTextBlocks.java
public class BeforeTextBlocks {
public static void main(String[] args) {
String json = "{\n" +
" \"name\": \"Ravi\",\n" +
" \"role\": \"Developer\"\n" +
"}";
System.out.println(json);
}
}Output:
{
"name": "Ravi",
"role": "Developer"
}
A text block lets the same payload be written the way it actually looks, with no escaped quotes and no manual newlines.
// File: AfterTextBlocks.java
public class AfterTextBlocks {
public static void main(String[] args) {
String json = """
{
"name": "Ravi",
"role": "Developer"
}""";
System.out.println(json);
}
}Output:
{
"name": "Ravi",
"role": "Developer"
}
Both versions produce the exact same String value — a text block only changes how the literal is written in source, not what kind of object results from it.
Syntax Rules
A text block opens with """ immediately followed by a line terminator — nothing but optional trailing whitespace may follow the opening delimiter on that line. Content begins on the next line, and closes with a """ that can either sit on its own line or attach directly to the end of the last content line.
Two things determine the final string value: an incidental whitespace algorithm strips the common leading indentation shared by every content line and the closing delimiter's line, and trailing whitespace is stripped from every line unless explicitly preserved. Whether the closing delimiter is on its own line changes whether the result ends in a trailing newline — attached to the last line, it does not; on its own line, it does.
One sentence before the diagram: the compiler measures the smallest indentation shared by every line, including the closing delimiter's line, and strips exactly that much from all of them.
Raw source lines (12 spaces each) After incidental-whitespace stripping
{ --> {
"name": "Ravi", --> "name": "Ravi",
"role": "Developer" --> "role": "Developer"
} --> }
^^^^^^^^^^^^
smallest common indent (12 spaces) — stripped from every line
Two escape sequences exist specifically for text blocks: \s represents a single space that survives trailing-whitespace stripping, and a \ at the very end of a line suppresses that line's newline entirely, joining it with the next line in the resulting string. Ordinary escapes like \n, \t, and \" still work exactly as they do in a regular string literal.
// File: TextBlockEscapesExample.java
public class TextBlockEscapesExample {
public static void main(String[] args) {
String withoutEscape = """
Left column:
""";
String withEscape = """
Left column:\s
""";
System.out.println("Without \\s length: " + withoutEscape.length());
System.out.println("With \\s length: " + withEscape.length());
}
}Output:
Without \s length: 13
With \s length: 14
\s adds exactly one character — a preserved trailing space that ordinary trailing-whitespace stripping would otherwise remove.
// File: TextBlockLineContinuationExample.java
public class TextBlockLineContinuationExample {
public static void main(String[] args) {
String message = """
This is a long line that is split across \
two lines in the source but stays one line \
in the resulting string.""";
System.out.println(message);
}
}Output:
This is a long line that is split across two lines in the source but stays one line in the resulting string.
The trailing \ at the end of the first two source lines suppresses their line terminators entirely, so the three source lines become one continuous line in the actual string value — the source is split purely for readability.
The closing """ on its own line adds a trailing newline to the resulting string; attached directly to the last line of content, it does not. This one placement choice is the most common source of an unexpected extra character in a text block's value.
Common Use Cases
JSON payloads, as shown in the AfterTextBlocks example above, are one of the most common uses — no more escaping every quote by hand.
SQL queries read naturally as a text block, keeping each clause on its own line exactly as it would appear in a query editor.
// File: SqlTextBlockExample.java
public class SqlTextBlockExample {
public static void main(String[] args) {
String query = """
SELECT id, name, email
FROM customers
WHERE status = 'ACTIVE'
ORDER BY name""";
System.out.println(query);
}
}Output:
SELECT id, name, email
FROM customers
WHERE status = 'ACTIVE'
ORDER BY name
Long single lines split for source readability, using the \ line-continuation escape shown above, without introducing unwanted line breaks into the actual string value.
Templated text with substituted values — covered in full in the real-world example below, combining a text block with String.formatted().
Basic Example
A small report card uses a text block as a template, with String.formatted() substituting values into it.
public class Main {
public static void main(String[] args) {
String name = "Ananya";
int score = 92;
String card = """
Name: %s
Score: %d
Result: %s""".formatted(name, score, score >= 40 ? "Pass" : "Fail");
System.out.println(card);
}
}Output
Name: Ananya Score: 92 Result: Pass
How it works:
- ›The text block starts after
"""and a line terminator, so its content —Name: %s,Score: %d,Result: %s— is written exactly as it should read, with no\ncharacters or+concatenation. - ›The incidental whitespace algorithm strips the common leading indentation shared by every content line, so the block's actual indentation in the source file does not leak into the resulting string.
- ›
.formatted(name, score, ...)substitutes each%s/%dplaceholder with an argument, in order, the same wayString.format()would. - ›Because the closing
"""is attached directly to the last content line rather than sitting on its own line, the resulting string has no trailing newline.
Combining Text Blocks With Other Features
String.formatted(), used above, is the natural pairing for a text block used as a template — the placeholders and the substituted values stay visually close together at the call site. A text block also pairs well with a record used as a simple template holder, or with a switch expression that selects between several fixed templates based on a sealed type, tying directly back to this series' Java 17 and Java 21 articles.
Best Practices
Keep every content line's indentation consistent relative to the others, and let the incidental whitespace algorithm handle the absolute indent level — the text block's result depends only on the least-indented line, not on how deeply the whole block sits inside the surrounding code.
Decide the closing delimiter's placement deliberately: put it on its own line when a trailing newline is wanted, and attach it directly to the last line of content when it is not.
Prefer String.formatted() or String.format() over manual concatenation for substituting values into a text-block template, exactly as PolicyRenewalEmailBuilder does here.
Reach for a text block whenever a string literal would otherwise need more than one or two escaped quotes or manual \n characters — a single embedded quote or short two-line message is often still clearer as a regular string literal.
Common Mistakes
Assuming content can start on the same line as the opening """ does not compile — the opening delimiter must be followed only by a line terminator, with the actual content starting on the next line.
// This does not compile - the opening delimiter must be followed only by
// a line terminator, not by content on the same line
String text = """Hello
World""";Misjudging how the closing delimiter's placement affects the resulting string is a second, genuinely runtime-demonstrable mistake — putting it on its own line adds a trailing newline that attaching it directly to the last line of content would not.
// File: TrailingNewlineMistake.java
public class TrailingNewlineMistake {
public static void main(String[] args) {
String value = """
token""";
String valueWithExtraLine = """
token
""";
System.out.println("value length: " + value.length());
System.out.println("valueWithExtraLine length: " + valueWithExtraLine.length());
}
}Output:
value length: 5
valueWithExtraLine length: 6
Both text blocks contain the same five-character word, but valueWithExtraLine picks up one extra character — a trailing newline — purely because its closing delimiter sits on its own line. A stray trailing newline like this is exactly the kind of thing that silently breaks an equals() comparison against a value read from elsewhere, such as a token or an ID.
Interview Questions
Q1. What is a text block in Java, and which version finalized it?
A text block is a multi-line string literal delimited by """, letting content such as JSON, SQL, or HTML be written without escaped quotes or manual \n characters. It was finalized in Java 15 via JEP 378, after two rounds as a preview feature in Java 13 and 14. Interviewers listen for whether you know it took two preview rounds, since that signals you have actually followed the JEP rather than just used the feature.
Q2. What determines how much leading whitespace is stripped from each line of a text block?
The compiler determines the minimum indentation among all non-blank content lines, plus the line containing the closing delimiter even if that line is otherwise blank, and strips exactly that much leading whitespace from every line. The nuance being tested is whether you remember the closing delimiter's line counts toward that minimum even when it holds no visible content.
Q3. How does the placement of the closing delimiter affect the resulting string's trailing newline?
If the closing """ sits on its own line, the resulting string ends with a trailing newline. If it is attached directly to the end of the last content line, no trailing newline is added — as demonstrated by the five-versus-six character length difference in this article's Common Mistakes section. This is one of the most practically useful things to demonstrate live in an interview, since it shows you have actually run into the bug.
Q4. What does the \s escape do inside a text block, and why is it needed?
\s represents a single space character that is preserved even though it sits at the end of a line — without it, ordinary trailing-whitespace stripping would remove a plain space in that position, since the stripping step runs before escape sequences are interpreted. Interviewers want to hear that ordering explicitly: stripping happens first, escape interpretation happens after.
Q5. What does a trailing backslash at the end of a line inside a text block do?
It suppresses that line's newline entirely, joining it directly with the next line in the resulting string value, while still allowing the source itself to be split across multiple lines for readability. The nuance is distinguishing this from \n, which inserts a newline rather than removing one.
Q6. Can a text block start with content immediately after the opening """ on the same line?
No. The opening delimiter must be followed only by a line terminator — content is required to start on the next line, and putting content on the same line as the opening """ is a compile error. This is a quick recall-style question service-based interviewers ask to confirm you have actually written text-block syntax before.
Q7. What method, added alongside text blocks, makes it easy to substitute values into a template?
String.formatted(Object...), added in Java 15, is an instance method equivalent to String.format(this, args) — it lets a text block used as a template be filled in with template.formatted(value1, value2, ...) directly at the call site. Product-based interviewers often follow up by asking why this reads better than String.format(template, ...) at the call site specifically.
FAQs
Is String.format() still needed, or does formatted() replace it for text blocks?
Both work identically — formatted() is simply an instance-method form of String.format(), added specifically so a template (often a text block) could have its substitutions written as a fluent call, template.formatted(...), instead of String.format(template, ...).
Do text blocks support string concatenation with +?
Yes. A text block produces a normal String object once compiled, so it can be concatenated with +, passed to any method expecting a String, and used anywhere a regular string literal would be used.
Can a text block be empty?
Yes, """ """ with only whitespace between the delimiters is valid and produces an empty string once the whitespace is stripped, though there is rarely a reason to prefer this over an ordinary empty string literal, "".
Does a text block always end with a newline?
No — only when the closing delimiter is on its own line. Attaching the closing delimiter directly to the last line of content produces a string with no trailing newline, exactly as value demonstrates in this article's Common Mistakes section.
Can text blocks be used for regular expressions?
Yes, and they are often clearer for a regex containing several literal backslashes, since a text block still requires \\ for a literal backslash exactly as a regular string literal does — text blocks change whitespace and quote handling, not backslash escaping rules.
Is there a performance difference between a text block and an equivalent concatenated string?
No. A text block is resolved entirely at compile time into an ordinary String constant, identical to what an equivalent hand-written concatenated literal would produce — there is no runtime cost difference at all.
Can a text block span more than the surrounding method's indentation without affecting the result?
Yes. The stripped indentation is based only on the minimum indentation among the text block's own lines, not on the surrounding code's indentation level, so indenting the whole block deeper or shallower than the code around it does not change the resulting string value.
Summary
Text blocks remove the two biggest sources of friction in multi-line string literals — escaped quotes and manually inserted \n characters — by letting content be written exactly as it should read, with the compiler handling indentation through the incidental whitespace algorithm. The two escapes unique to text blocks, \s for a preserved trailing space and a trailing \ for line continuation, exist specifically to handle the cases the automatic stripping would otherwise get in the way of.
The habit worth carrying forward from this article's policy-renewal example is deciding the closing delimiter's placement deliberately — on its own line when a trailing newline is wanted, attached to the last line when it is not — since that one placement choice is the single most common source of an unexpected extra character in a text block's value.
What to Read Next
Learn how to check and cast a type in a single step.