Part 3 of 14

Text Blocks (JEP 378): Multiline Strings Without the Escape Hell

Finalized in Java 15 (JEP 378). Available in all Java 15+ releases, including Java 17. Previous previews: Java 13 (JEP 355) and Java 14 (JEP 368).

The Problem: Embedded Strings in Java

Writing multi-line strings in Java has always been painful:

// JSON — a wall of escape sequences and concatenation
String json = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"role\": \"engineer\",\n" +
    "  \"active\": true\n" +
    "}";

// SQL — unreadable indentation and newlines
String sql = "SELECT u.name, u.email\n" +
    "FROM users u\n" +
    "JOIN orders o ON u.id = o.user_id\n" +
    "WHERE o.status = 'ACTIVE'\n" +
    "ORDER BY u.name";

// HTML — hard to edit without IDE support
String html = "<html>\n" +
    "  <body>\n" +
    "    <p>Hello, world!</p>\n" +
    "  </body>\n" +
    "</html>\n";

These strings are:

  • Hard to read — the escape noise obscures the actual content
  • Easy to break — a missing \n or misplaced \" causes subtle bugs
  • Hard to maintain — reformatting the SQL or HTML requires touching every line

Text Blocks solve this completely.


What Is a Text Block?

A text block is a string literal delimited by triple double-quotes """. It can span multiple lines without escape sequences for newlines or double quotes.

String json = """
        {
          "name": "Alice",
          "role": "engineer",
          "active": true
        }
        """;

This json variable contains exactly:

{
  "name": "Alice",
  "role": "engineer",
  "active": true
}

Note: No leading spaces (indentation is stripped). No trailing newline at the end of the last line before the closing """. This is controlled by the indentation algorithm.


Syntax Rules

The Opening Delimiter

The opening """ must be followed by a newline (whitespace then newline is also allowed):

// Valid
String s = """
    content
    """;

// Invalid — content must start on the next line
String s = """content""";  // compile error

The Closing Delimiter

The closing """ position determines the amount of indentation stripped:

// Closing """ at column 0 — no indentation stripped
String s = """
content
""";
// s = "content\n"

// Closing """ indented 4 spaces — 4 spaces stripped from each line
String s = """
    content
    line 2
    """;
// s = "content\nline 2\n"

The Indentation Algorithm

This is the most important concept in text blocks. Java strips incidental whitespace — the indentation that exists only because the text block is inside an indented class or method.

The algorithm:

  1. Compute the common leading whitespace across all non-empty lines, including the closing """
  2. Strip that many spaces from the beginning of each line

Example:

public class Example {
    void method() {
        String s = """
                line 1
                line 2
                """;
    }
}

The opening """ is at column 16 (inside method body). Each content line starts at column 16. The closing """ is also at column 16. So 16 characters of leading whitespace are stripped from each line:

line 1
line 2

(Trailing newline present because the closing """ is on its own line.)

Moving the closing """ changes stripping:

String s = """
        line 1
        line 2
""";
// Closing """ at column 0 → common prefix is 0 → no stripping
// s = "        line 1\n        line 2\n"
String s = """
        line 1
        line 2
    """;
// Closing """ at column 4 → strip 4 spaces from each line
// s = "    line 1\n    line 2\n"

Rule of thumb: put the closing """ at the indentation level of the content you want in the final string, or one level out to strip content indentation entirely.


Trailing Whitespace

By default, text blocks strip trailing whitespace from each line. This ensures that lines with no visible characters don’t contain invisible trailing spaces.

If you need intentional trailing whitespace (rare), use \s:

String s = """
    line with trailing spaces   \s
    next line
    """;
// "line with trailing spaces   \nnext line\n"

\s is a new escape sequence added specifically for text blocks. It represents a space but prevents the trailing whitespace stripper from removing preceding spaces.


Escape Sequences in Text Blocks

Most escape sequences work normally. New in Java 15:

SequenceMeaning
\sA space that prevents trailing whitespace removal
\ at end of lineLine continuation — suppresses the newline

Line Continuation

String s = """
    This is a very long line that I want to \
    continue on the next line without a newline character.
    """;
// s = "This is a very long line that I want to continue on the next line without a newline character.\n"

Useful for code generation or long prose without introducing line breaks.

Quoting in Text Blocks

You do not need to escape " inside text blocks (unless you have three consecutive " marks):

String json = """
    {"key": "value", "other": "test"}
    """;
// No \" needed — works directly

// Three consecutive quotes must be escaped
String s = """
    She said \"\"\"hello\"\"\".
    """;
// or escape just the first:
String s2 = """
    She said \"""hello""".
    """;

Real-World Examples

JSON

String request = """
        {
          "userId": "%s",
          "action": "login",
          "timestamp": %d
        }
        """.formatted(userId, System.currentTimeMillis());

SQL

String query = """
        SELECT
            u.id,
            u.name,
            u.email,
            COUNT(o.id) AS order_count
        FROM users u
        LEFT JOIN orders o ON u.id = o.user_id
        WHERE u.active = true
          AND u.created_at > :cutoff
        GROUP BY u.id, u.name, u.email
        ORDER BY order_count DESC
        LIMIT :limit
        """;

The SQL is readable, has proper indentation, and is easy to modify without touching escape sequences.

HTML (for email templates, test fixtures)

String html = """
        <!DOCTYPE html>
        <html>
          <head><title>Welcome</title></head>
          <body>
            <h1>Hello, %s!</h1>
            <p>Your account is now active.</p>
          </body>
        </html>
        """.formatted(userName);

YAML (for test configuration)

String config = """
        server:
          port: 8080
          host: localhost
        database:
          url: jdbc:postgresql://localhost/mydb
          pool-size: 10
        """;

The formatted() Method

String.formatted() was introduced in Java 15 alongside text blocks. It is an instance-method equivalent of String.format():

// Java 11 style
String s = String.format("Hello, %s! You are %d years old.", name, age);

// Java 15+ — cleaner with text blocks
String s = """
        Hello, %s!
        You are %d years old.
        """.formatted(name, age);

formatted() returns a new String — it does not modify the text block itself (strings are immutable).

Strip and Indent Methods

Java 15 also added:

// String.stripIndent() — applies the text block indentation algorithm to an existing string
String indented = "    hello\n    world\n";
System.out.println(indented.stripIndent());
// "hello\nworld\n"

// String.translateEscapes() — processes escape sequences in a string as if it were a text block
String withEscapes = "line1\\nline2";
System.out.println(withEscapes.translateEscapes());
// "line1\nline2"

Text Blocks vs String.format vs MessageFormat

ApproachReadabilityType safetyLocalization
+ concatenationPoorN/APoor
String.format()MediumNoVia MessageFormat
Text block + formatted()GoodNoVia MessageFormat
Template Strings (Java 21+)BestYesYes

For Java 17, text blocks with formatted() is the best available option for embedded string templates. Java 21’s String Templates (JEP 430, later withdrawn and re-proposed) will eventually supersede this pattern.


Common Mistakes

Mistake 1: Starting Content on the """ Line

// Compile error
String s = """hello
    world""";

Content must start on the line after the opening """.

Mistake 2: Unexpected Indentation in Output

class Foo {
    static String s = """
        content
""";  // closing """ at column 0 → no stripping → content has 8 spaces
}

Place the closing """ at the expected indentation level, not at column 0.

Mistake 3: Unexpected Trailing Newline

String s = """
    content
    """;
// s ends with "\n" because closing """ is on its own line

String s2 = """
    content""";  // compile error — must have newline after opening """

If you do not want a trailing newline, use stripTrailing():

String s = """
    content
    """.stripTrailing();
// s = "content" — no trailing newline

Or use the line continuation at the end:

String s = """
    content\
    """;
// s = "content" — backslash-newline at end suppresses the newline

Mistake 4: Assuming Text Blocks Are Different at Runtime

Text blocks are just String values. A text block compiles to the same bytecode as an equivalent concatenated string literal. There is no runtime performance difference.


Text Blocks in Tests

Text blocks dramatically improve test readability for JSON comparison:

@Test
void userToJsonTest() {
    User user = new User("alice", "alice@example.com");
    String actual = objectMapper.writeValueAsString(user);

    String expected = """
            {
              "username": "alice",
              "email": "alice@example.com"
            }
            """.stripTrailing();

    assertEquals(expected, actual);
}

Compare this to the Java 11 equivalent — the improvement in readability is dramatic.


Summary

FeatureBehavior
Opening """Must be followed by a newline
Indentation strippingControlled by closing """ position
Trailing whitespaceAutomatically stripped per line
\s escapeForces a trailing space, prevents stripping
\ at line endLine continuation — removes the newline
" insideNo escaping needed (except for three consecutive ")
formatted()Instance method for %s, %d substitution
Runtime typeIdentical to String; no runtime overhead

What’s Next

Article 4: Records (JEP 395) covers Java’s concise immutable data class syntax — one of the most impactful language features in years.