3.4 Text Blocks And Templates

Text blocks, introduced in Java 15, revolutionize how we handle multi-line strings. Combined with modern formatting methods, they eliminate the verbosity and error-prone nature of string concatenation and manual escaping.

The Problem with Traditional Strings

Before Text Blocks

// JSON with manual escaping
String json = "{\n" +
              "  \"name\": \"Alice\",\n" +
              "  \"age\": 30,\n" +
              "  \"email\": \"alice@example.com\"\n" +
              "}";

// SQL with concatenation
String query = "SELECT users.id, users.name, orders.total\n" +
               "FROM users\n" +
               "JOIN orders ON users.id = orders.user_id\n" +
               "WHERE users.status = 'ACTIVE'\n" +
               "ORDER BY orders.total DESC";

// HTML with escaping
String html = "<html>\n" +
              "  <head>\n" +
              "    <title>Welcome</title>\n" +
              "  </head>\n" +
              "  <body>\n" +
              "    <h1>Hello, World!</h1>\n" +
              "  </body>\n" +
              "</html>";

Problems:

  1. Manual \n insertion for newlines
  2. Quote escaping (\")
  3. Hard to visualize the actual output
  4. Error-prone: easy to miss + or quotes
  5. Difficult to maintain: editing requires changing multiple lines

Text Block Syntax

Text blocks use triple quotes (""") as delimiters:

String json = """
    {
      "name": "Alice",
      "age": 30,
      "email": "alice@example.com"
    }
    """;

Key features:

  • No \n needed (implicit newlines)
  • Quotes don't need escaping
  • WYSIWYG: code matches output
  • Multi-line editing is easy

Indentation Rules

Text blocks automatically remove common leading whitespace based on the position of the closing delimiter.

Closing Delimiter Position Determines Indentation

// Closing delimiter at column 0: no leading whitespace
String html = """
    <html>
      <body>
        <h1>Welcome</h1>
      </body>
    </html>
    """;
// Result: "<html>\n  <body>\n    <h1>Welcome</h1>\n  </body>\n</html>\n"

Visualization:

Column:  0123456789...
         """
         ____<html>        (4 spaces leading)
         ______<body>      (6 spaces leading)
         ________<h1>...   (8 spaces leading)
         ______</body>     (6 spaces leading)
         ____</html>       (4 spaces leading)
         """               (0 spaces - this determines removal)

Common whitespace removal: 4 spaces removed from all lines, preserving relative indentation.

Preserving Indentation

// Keep all indentation: indent closing delimiter
String indented = """
          Line 1
            Line 2
              Line 3
          """;
// Closing """ at 10 spaces: removes 10 spaces from all lines
// Result: "Line 1\n  Line 2\n    Line 3\n"

Explicit Control with \s and \

\s: Explicit space (prevents removal)

String preserveSpaces = """
    Line 1\s\s
    Line 2\s
    """;
// Result: "Line 1  \nLine 2 \n" (trailing spaces preserved)

\: Line continuation (no newline)

String oneLine = """
    This is a long sentence \
    that continues on the next line \
    without newlines.
    """;
// Result: "This is a long sentence that continues on the next line without newlines.\n"

Escape Sequences

Text blocks support standard escape sequences:

String text = """
    Tab:\tIndented
    Quote: \"Hello\"
    Backslash: \\
    Unicode: \u00A9 (copyright symbol)
    """;

Common escapes:

  • \n - Newline (usually implicit, but can use explicitly)
  • \t - Tab
  • \" - Double quote (optional in text blocks)
  • \\ - Backslash
  • \s - Space (prevents trimming)
  • \ - Line continuation

String Formatting with formatted()

The formatted() method applies printf-style formatting to strings:

String name = "Alice";
int age = 30;

String message = """
    {
      "name": "%s",
      "age": %d,
      "active": %b
    }
    """.formatted(name, age, true);

Common format specifiers:

Specifier Type Example Output
%s String "Hello".formatted("World") "Hello"
%d Integer "%d".formatted(42) "42"
%f Float/Double "%.2f".formatted(3.14159) "3.14"
%b Boolean "%b".formatted(true) "true"
%n Platform newline "Line 1%nLine 2" OS-specific newline
%x Hex integer "%x".formatted(255) "ff"
%% Literal % "100%%".formatted() "100%"

Precision and width:

double price = 19.5;
String formatted = """
    Price: $%.2f
    """.formatted(price);
// Result: "Price: $19.50\n"

int count = 42;
String padded = """
    Count: %5d
    """.formatted(count);
// Result: "Count:    42\n" (padded to 5 characters)

Real-World Use Cases

1. JSON Payloads

public String createUserPayload(String email, String name, boolean active) {
    return """
        {
          "event": "user.created",
          "timestamp": "%s",
          "data": {
            "email": "%s",
            "name": "%s",
            "active": %b
          }
        }
        """.formatted(Instant.now(), email, name, active);
}

2. SQL Queries

public String buildQuery(String status, int limit) {
    return """
        SELECT 
            users.id,
            users.name,
            users.email,
            COUNT(orders.id) as order_count
        FROM users
        LEFT JOIN orders ON users.id = orders.user_id
        WHERE users.status = '%s'
        GROUP BY users.id, users.name, users.email
        HAVING COUNT(orders.id) > 0
        ORDER BY order_count DESC
        LIMIT %d
        """.formatted(status, limit);
}

Security Note: This example is simplified. In production, use prepared statements to prevent SQL injection:

String query = """
    SELECT * FROM users
    WHERE status = ?
      AND created_at > ?
    ORDER BY created_at DESC
    """;

try (var stmt = connection.prepareStatement(query)) {
    stmt.setString(1, status);
    stmt.setTimestamp(2, Timestamp.from(instant));
    // Execute safely
}

3. HTML Templates

public String renderPage(String title, String heading, List<String> items) {
    var itemsList = items.stream()
        .map(item -> "      <li>%s</li>".formatted(item))
        .collect(Collectors.joining("\n"));

    return """
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>%s</title>
        </head>
        <body>
            <h1>%s</h1>
            <ul>
        %s
            </ul>
        </body>
        </html>
        """.formatted(title, heading, itemsList);
}

4. Configuration Files

public String generateYamlConfig(String env, String dbHost, int dbPort) {
    return """
        environment: %s

        database:
          host: %s
          port: %d
          pool:
            min: 5
            max: 20
          ssl: true

        logging:
          level: INFO
          format: json
        """.formatted(env, dbHost, dbPort);
}

5. Test Data

@Test
void testCsvParsing() {
    String csvData = """
        id,name,email,status
        1,Alice,alice@example.com,ACTIVE
        2,Bob,bob@example.com,INACTIVE
        3,Charlie,charlie@example.com,ACTIVE
        """;

    List<User> users = CsvParser.parse(csvData);
    assertEquals(3, users.size());
}

6. Regular Expressions (Verbose Mode)

// Email regex with comments (use (?x) flag for verbose mode)
String emailPattern = """
    (?x)                     # Enable verbose mode
    ^                        # Start of string
    [A-Za-z0-9+_.-]+         # Local part
    @                        # @ symbol
    [A-Za-z0-9.-]+           # Domain name
    \\.                      # Dot
    [A-Za-z]{2,}             # TLD
    $                        # End of string
    """;

Pattern pattern = Pattern.compile(emailPattern);

Advanced Techniques

Multiline String Concatenation

String part1 = """
    {
      "section": "intro",
    """;

String part2 = """
      "content": "Welcome!"
    }
    """;

String combined = part1 + part2;
// Result: Valid JSON from two text blocks

Escaping Triple Quotes

String markdown = """
    To use text blocks in Java, write:

    ```java
    String s = \"""
        Text here
        \""";
""";

**Use `\"""` to include triple quotes in text block.**

#### Conditional Content with Ternary

```java
boolean isAdmin = true;

String json = """
    {
      "user": "alice",
      "role": "%s"%s
    }
    """.formatted(
        isAdmin ? "admin" : "user",
        isAdmin ? ",\n  \"permissions\": [\"read\", \"write\", \"delete\"]" : ""
    );

Dynamic Lists with Streams

List<String> tags = List.of("java", "programming", "tutorial");

String html = """
    <div class="tags">
    %s
    </div>
    """.formatted(
        tags.stream()
            .map(tag -> "  <span>%s</span>".formatted(tag))
            .collect(Collectors.joining("\n"))
    );

String Templates (Preview - Java 21+)

Note: String templates are a preview feature in Java 21 and may change.

String templates allow embedded expressions directly in strings:

String name = "Alice";
int age = 30;

// Traditional
String msg = "Hello, " + name + "! You are " + age + " years old.";

// String template (preview)
String msg = STR."Hello, \{name}! You are \{age} years old.";

Template processors:

  • STR: String interpolation
  • FMT: Formatted interpolation
  • RAW: Raw template without processing

Example with FMT:

double price = 19.95;
String formatted = FMT."Price: $%.2f\{price}";
// Result: "Price: $19.95"

Custom processors:

// JSON template processor
var json = JSON."""
    {
      "name": "\{name}",
      "age": \{age}
    }
    """;

String templates are not finalized as of Java 25—check current JDK release notes for status.

Common Pitfalls

Pitfall 1: Trailing Whitespace

// Trailing spaces are trimmed by default
String text = """
    Line 1    
    Line 2    
    """;
// Result: "Line 1\nLine 2\n" (no trailing spaces)

// Preserve with \s
String preserved = """
    Line 1\s\s
    Line 2\s
    """;
// Result: "Line 1  \nLine 2 \n"

Pitfall 2: Empty First/Last Lines

// Empty line after opening """
String text = """

    Content
    """;
// Result: "\nContent\n" (includes leading newline)

// Empty line before closing """
String text = """
    Content

    """;
// Result: "Content\n\n" (includes trailing newline)

Pitfall 3: Indentation Miscalculation

// Inconsistent indentation
String bad = """
        Line 1
      Line 2    // Less indented than others
        Line 3
    """;
// Closing """ at 4 spaces: removes 4 spaces from all
// Result: "    Line 1\n  Line 2\nLine 3\n" (relative indentation preserved)

Pitfall 4: Mixing Tabs and Spaces

// Don't mix tabs and spaces!
String mixed = """
    <tab>Line 1   // Tab character
        Line 2    // Spaces
    """;
// Indentation calculation is character-based, not visual

Best practice: Use spaces consistently (most IDEs default to spaces).

Performance Considerations

Text blocks have no runtime performance penalty:

  • Compiled to string constants (same as regular strings)
  • Interned in string pool
  • Indentation removal happens at compile time
// These produce identical bytecode:
String a = "Line 1\nLine 2\n";
String b = """
    Line 1
    Line 2
    """;

Memory: Text blocks don't increase memory usage—they're equivalent to concatenated strings.

Best Practices

  1. Use for multi-line strings: JSON, SQL, HTML, XML, YAML, test data
  2. Position closing delimiter carefully: Determines indentation removal
  3. Use formatted() for dynamic content: Type-safe, printf-style formatting
  4. Avoid for single-line strings: Use regular strings for simplicity
  5. Use \s to preserve trailing spaces when needed
  6. Use \ for line continuation without newlines
  7. Keep indentation consistent: Use spaces, not tabs
  8. Extract complex formatting to methods: Don't inline too much logic

Comparison with Other Languages

Language Syntax Escape Required Interpolation
Java """...""" \" optional, \ for continuation formatted()
Python """...""" or '''...''' \" optional f-strings: f"{var}"
JavaScript `...` (template literals) ``` required ${var}
Kotlin """...""" No escaping ${var}
C# @"..." (verbatim) "" for quotes $"..." (interpolation)

Java's text blocks are most similar to Python's triple-quoted strings, with formatting via formatted() rather than built-in interpolation.

Migration Checklist

Identify candidates for text blocks:

  • ✅ Strings with \n (newlines)
  • ✅ Strings with \" (escaped quotes)
  • ✅ Multiple string concatenations with +
  • ✅ Embedded formats (JSON, SQL, HTML, XML)
  • ✅ Multi-line test data
  • ❌ Single-line strings (keep as-is)
  • ❌ Dynamic strings built in loops (use StringBuilder or streams)

Conclusion

Text blocks transform how we work with multi-line strings in Java:

  • WYSIWYG: Code matches output
  • No manual escaping: Quotes and newlines work naturally
  • Compile-time formatting: Indentation removed automatically
  • Type-safe formatting: formatted() provides printf-style placeholders
  • Zero runtime cost: Equivalent to string literals

Combined with modern formatting methods, text blocks make embedded formats readable and maintainable, eliminating an entire class of string-related bugs and improving code clarity.

Next: Putting it all together with comprehensive examples and migration strategies.