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:
- Manual
\ninsertion for newlines - Quote escaping (
\") - Hard to visualize the actual output
- Error-prone: easy to miss
+or quotes - 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
\nneeded (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 interpolationFMT: Formatted interpolationRAW: 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
- Use for multi-line strings: JSON, SQL, HTML, XML, YAML, test data
- Position closing delimiter carefully: Determines indentation removal
- Use
formatted()for dynamic content: Type-safe, printf-style formatting - Avoid for single-line strings: Use regular strings for simplicity
- Use
\sto preserve trailing spaces when needed - Use
\for line continuation without newlines - Keep indentation consistent: Use spaces, not tabs
- 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.