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.
1// File: BeforeTextBlocks.java
2
3public class BeforeTextBlocks {
4 public static void main(String[] args) {
5 String json = "{\n" +
6 " \"name\": \"Ravi\",\n" +
7 " \"role\": \"Developer\"\n" +
8 "}";
9
10 System.out.println(json);
11 }
12}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.
1// File: AfterTextBlocks.java
2
3public class AfterTextBlocks {
4 public static void main(String[] args) {
5 String json = """
6 {
7 "name": "Ravi",
8 "role": "Developer"
9 }""";
10
11 System.out.println(json);
12 }
13}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.
1// File: TextBlockEscapesExample.java
2
3public class TextBlockEscapesExample {
4 public static void main(String[] args) {
5 String withoutEscape = """
6 Left column:
7 """;
8
9 String withEscape = """
10 Left column:\s
11 """;
12
13 System.out.println("Without \\s length: " + withoutEscape.length());
14 System.out.println("With \\s length: " + withEscape.length());
15 }
16}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.
1// File: TextBlockLineContinuationExample.java
2
3public class TextBlockLineContinuationExample {
4 public static void main(String[] args) {
5 String message = """
6 This is a long line that is split across \
7 two lines in the source but stays one line \
8 in the resulting string.""";
9
10 System.out.println(message);
11 }
12}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.
1// File: SqlTextBlockExample.java
2
3public class SqlTextBlockExample {
4 public static void main(String[] args) {
5 String query = """
6 SELECT id, name, email
7 FROM customers
8 WHERE status = 'ACTIVE'
9 ORDER BY name""";
10
11 System.out.println(query);
12 }
13}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().
Real-World Example
An insurance policy renewal reminder needs a multi-paragraph email body with several values substituted into a fixed template — a natural fit for a text block combined with String.formatted(), added to the JDK alongside text blocks in Java 15.
1// File: PolicyRenewalEmailBuilder.java
2
3public class PolicyRenewalEmailBuilder {
4
5 public String build(String customerName, String policyNumber, String vehicleNumber,
6 double premiumAmount, String dueDate) {
7 String template = """
8 Dear %s,
9
10 Your motor insurance policy %s for vehicle %s is due for renewal on %s.
11 The renewal premium is Rs. %.2f.
12
13 Renew before the due date to avoid a lapse in coverage.
14
15 Regards,
16 Policy Renewals Team""";
17
18 return template.formatted(customerName, policyNumber, vehicleNumber, dueDate, premiumAmount);
19 }
20}1// File: PolicyRenewalEmailDemo.java
2
3public class PolicyRenewalEmailDemo {
4 public static void main(String[] args) {
5 var builder = new PolicyRenewalEmailBuilder();
6
7 String email = builder.build("Vikram", "POL-88214", "KA-05-MN-4021", 4599.5, "2026-09-10");
8
9 System.out.println(email);
10 }
11}Output:
Dear Vikram,
Your motor insurance policy POL-88214 for vehicle KA-05-MN-4021 is due for renewal on 2026-09-10.
The renewal premium is Rs. 4599.50.
Renew before the due date to avoid a lapse in coverage.
Regards,
Policy Renewals Team
A mistake that appears often in fresher pull requests is building a multi-line email or SQL string with repeated + concatenation and manually placed \n characters, where a single missing space or misplaced newline silently breaks the formatting and often is not caught until a real user complains about a garbled email. Defining the template once as a text block, exactly as PolicyRenewalEmailBuilder does here, keeps the formatting visually obvious in the source itself, matching what actually gets sent.
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.
1// This does not compile - the opening delimiter must be followed only by
2// a line terminator, not by content on the same line
3String text = """Hello
4 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.
1// File: TrailingNewlineMistake.java
2
3public class TrailingNewlineMistake {
4 public static void main(String[] args) {
5 String value = """
6 token""";
7
8 String valueWithExtraLine = """
9 token
10 """;
11
12 System.out.println("value length: " + value.length());
13 System.out.println("valueWithExtraLine length: " + valueWithExtraLine.length());
14 }
15}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.