Optional & Modern Java

Text Blocks

15 min Lesson 7 of 13

Text Blocks

Before Java 15 made text blocks a permanent feature, embedding multi-line strings in Java code was painful. JSON bodies, SQL queries, HTML fragments, and XML payloads all had to be written as a tangle of concatenated single-line strings with manual \n and escaped quotes everywhere. Text blocks, introduced as a preview in Java 13 and finalised in Java 15, solve this problem with a clean, readable syntax.

The Old Way vs. The New Way

Here is the same JSON string written both ways:

// Java 14 and earlier — hard to read, easy to make mistakes String json = "{\n" + " \"name\": \"Alice\",\n" + " \"age\": 30,\n" + " \"active\": true\n" + "}"; // Java 15+ — text block String json = """ { "name": "Alice", "age": 30, "active": true } """;

The second version is what the source file actually looks like — indentation included — without a single escaped quote or newline escape sequence.

Syntax Rules

A text block opens with three double-quotes (""") followed by optional whitespace and then a mandatory newline. The opening """ cannot be immediately followed by content on the same line. The closing """ can either sit on its own line (adding a trailing newline to the value) or follow the last line of content directly (no trailing newline).

// Trailing newline IS included — closing """ on its own line String withTrailingNewline = """ hello """; // Trailing newline is NOT included — closing """ on same line as content String withoutTrailingNewline = """ hello"""; System.out.println(withTrailingNewline.length()); // 6 (h-e-l-l-o-\n) System.out.println(withoutTrailingNewline.length()); // 5 (h-e-l-l-o)
The opening """ line is never part of the string content. Its only job is to start the block and anchor the indentation baseline. The content begins on the very next line.

Incidental Whitespace — The Key Concept

The most important thing to understand about text blocks is how Java handles indentation. If you indent a text block eight spaces to match your surrounding code, Java does not blindly include all eight spaces in the string value. Instead, it strips incidental whitespace — the common leading whitespace shared by every content line and the closing """.

The closing """ acts as an indentation anchor. Java looks at the column of the closing delimiter and removes that many leading spaces from every content line.

String sql = """ SELECT id, name FROM users WHERE active = 1 """; // The closing """ is at column 8 (0-indexed). // Java strips 8 leading spaces from each content line. // The resulting string is: // "SELECT id, name\nFROM users\nWHERE active = 1\n" // — no leading spaces at all. System.out.println(sql); // SELECT id, name // FROM users // WHERE active = 1

You can deliberately keep some leading whitespace by positioning the closing """ to the left of the content:

String indented = """ line one line two """; // closing """ at column 8; content at column 12 // => 4 spaces of leading whitespace are preserved on each line // " line one\n line two\n"
Best practice: align the closing """ with the opening indentation of the text block itself. This produces zero incidental whitespace in the output and makes the intent obvious at a glance.

Escape Sequences and New Additions

All standard Java escape sequences work inside text blocks: \t, \\, \uXXXX, and so on. Because double quotes no longer need escaping inside a text block, you only need to escape a " if it is part of a sequence of three or more consecutive quotes.

Java 14 also introduced two new escape sequences specifically for text blocks:

  • \<line-terminator> — a backslash at the end of a source line suppresses the newline at that point, letting you split a long logical line across multiple source lines without embedding an actual newline in the string.
  • \s — a single space that is never stripped by incidental-whitespace trimming. Useful to force trailing spaces on a line.
// Line continuation: the value is one long line, not three String query = """ SELECT id, name, email \ FROM users \ WHERE active = 1"""; // equivalent to: "SELECT id, name, email FROM users WHERE active = 1" // \s preserves a trailing space that would otherwise be trimmed String aligned = """ RED \s GREEN\s BLUE \s """;

String.formatted() — Interpolation Without a New Operator

Java still has no built-in string interpolation, but text blocks pair naturally with String.formatted() (an instance method added in Java 15 that calls String.format on the receiver):

String template = """ INSERT INTO orders (customer_id, amount, status) VALUES (%d, %.2f, '%s'); """; String sql = template.formatted(42, 199.99, "PENDING"); System.out.println(sql); // INSERT INTO orders (customer_id, amount, status) // VALUES (42, 199.99, 'PENDING');

Practical Example — Embedding HTML

String html = """ <!DOCTYPE html> <html lang="en"> <head><title>%s</title></head> <body> <h1>%s</h1> </body> </html> """.formatted("Dashboard", "Welcome back!"); System.out.println(html);
Text blocks are still String objects. They are not a new type, they are not lazy, and they are not templates. All string operations — equals(), substring(), replaceAll() — work identically. The compiler processes the incidental whitespace and escape sequences at compile time; at runtime a text block is just a regular String constant.

When to Use Text Blocks

  • SQL queries longer than one clause — indentation matches logical structure.
  • JSON / XML payloads in tests or configuration code.
  • HTML or SVG fragments returned from helper methods.
  • Expected output strings in unit tests — comparing against a readable block instead of a single-line blob makes failures easier to diagnose.
  • Log message templates with placeholders.

For simple one-liners or dynamically assembled strings, regular string literals and StringBuilder remain perfectly fine.

Summary

Text blocks let you write multi-line strings exactly as they should appear, without escape sequences or concatenation noise. The critical mechanic is incidental whitespace stripping controlled by the column position of the closing """. Pair text blocks with .formatted() for lightweight parameterisation, use \ line continuation to suppress unwanted newlines, and remember that the result is still a plain String — all existing APIs apply unchanged.