Part 5 of 6 | For senior Java developers modernizing codebases to Java 17
The Problem Text Blocks Solve
Before Java 15, embedding multi-line strings in Java code was a nightmare of escape sequences, concatenation operators, and unreadable structure. Every JSON fixture, SQL query, or HTML template required extensive reformatting:
// The old way - escape sequence hell
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30,\n" +
" \"email\": \"alice@example.com\"\n" +
"}";
String query = "SELECT u.name, u.email, o.total\n" +
"FROM users u\n" +
"JOIN orders o ON u.id = o.user_id\n" +
"WHERE o.status = 'COMPLETED'\n" +
"ORDER BY o.created_at DESC";
Text blocks eliminate this pain entirely while introducing critical security considerations that every senior developer must understand.
Why This Matters
Readability Crisis: Traditional multi-line strings obscure the actual data structure with Java syntax noise (+, \n, \"), making maintenance difficult and copy-pasting from external tools (Postman, SQL clients) a reformatting burden.
Security Landmines: Text blocks combined with formatted() create powerful template capabilities, but misuse with SQL queries or HTML content introduces SQL injection and XSS vulnerabilities that can compromise production systems.
Maintenance Burden: Every content update requires modifying multiple lines with careful attention to escape sequences and concatenation operators—a recipe for bugs.
What Are Text Blocks?
Text blocks use triple-double-quote delimiters (""") to create multi-line string literals that automatically normalize line terminators, strip common indentation, and eliminate most escape sequences:
// The new way - clean and readable
String json = """
{
"name": "Alice",
"age": 30,
"email": "alice@example.com"
}
""";
String query = """
SELECT u.name, u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'COMPLETED'
ORDER BY o.created_at DESC
""";
Key benefits:
- No escape sequences for quotes or newlines
- Natural indentation preserved
- Copy-paste friendly from external tools
- Zero runtime performance overhead
When to Use Text Blocks
✅ Perfect for:
- JSON, XML, HTML structures in test fixtures
- SQL queries (especially complex CTEs)
- Email templates and invoices
- Configuration files and API responses
- Any multi-line literal in embedded languages
❌ Avoid for:
- Single-line strings (use regular
"...") - Platform-specific line endings (text blocks normalize to
\n) - Strings requiring immediate complex processing
Real-World Examples
Example 1: JSON Test Fixtures
public static String getUserJSONFormatted(String name, int age, String email) {
return """
{
"name": "%s",
"age": %d,
"email": "%s"
}""".formatted(name, age, email);
}
// Usage
String json = getUserJSONFormatted("Charlie", 25, "charlie@example.com");
Output:
{
"name": "Charlie",
"age": 25,
"email": "charlie@example.com"
}
Example 2: HTML Templates Without Escape Hell
public static String getDashboardHTML() {
return """
<html>
<head>
<title>User Dashboard</title>
</head>
<body>
<h1>Welcome, User!</h1>
<p>This is your dashboard.</p>
</body>
</html>""";
}
No more escaping quotes! The structure is immediately clear, matching exactly what you'd see in design tools.
Example 3: Email Template System
public static String renderEmailTemplate(String recipientName, String activationUrl, String expirationHours) {
return """
<html>
<body style="font-family: Arial, sans-serif;">
<h2>Welcome, %s!</h2>
<p>Thank you for signing up. Please activate your account within %s hours.</p>
<p><a href="%s" style="background-color: #007bff; color: white; padding: 10px 20px;">Activate Account</a></p>
<p>Best regards,<br>The Team</p>
</body>
</html>""".formatted(recipientName, expirationHours, activationUrl);
}
Text blocks combined with formatted() create lightweight template systems without external dependencies—perfect for emails, invoices, and configurations.
Example 4: Complex SQL with CTEs
public static String getTopCustomersQuerySafe() {
return """
WITH customer_totals AS (
SELECT u.id, u.name, u.email,
SUM(o.total) as total_spent,
COUNT(o.order_id) as order_count
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = ?
AND o.created_at >= ?
GROUP BY u.id, u.name, u.email
)
SELECT id, name, email, total_spent, order_count
FROM customer_totals
WHERE total_spent >= ?
ORDER BY total_spent DESC
LIMIT ?""";
}
Notice the ? placeholders—this demonstrates the secure pattern for SQL with text blocks.
Security Considerations (CRITICAL)
SQL Injection - The Danger Zone
NEVER use formatted() with SQL queries and user input:
// ❌ DANGEROUS - SQL Injection Vulnerability!
String status = userInput; // User enters: "COMPLETED' OR '1'='1"
String sql = """
SELECT * FROM orders
WHERE status = '%s'
""".formatted(status);
// Resulting SQL: SELECT * FROM orders WHERE status = 'COMPLETED' OR '1'='1'
// This returns ALL orders, bypassing security!
Worse attacks can delete entire tables:
// ❌ If user enters: "'; DROP TABLE orders; --"
String sql = """
SELECT * FROM orders WHERE status = '%s'
""".formatted(maliciousInput);
// Your entire orders table is DELETED!
The Safe SQL Pattern
Always use PreparedStatement with text blocks:
// ✅ SAFE - PreparedStatement prevents SQL injection
String query = """
SELECT * FROM orders
WHERE status = ?
AND total >= ?
""";
try (PreparedStatement ps = connection.prepareStatement(query)) {
ps.setString(1, userInput); // Automatically escaped - safe!
ps.setDouble(2, minAmount);
try (ResultSet rs = ps.executeQuery()) {
// Process results
}
}
Key points:
- Text blocks improve SQL readability
- PreparedStatement prevents injection
- Use
?placeholders, NOT%sformatting - Set parameters via
setString(),setInt(),setDouble(), etc.
XSS Injection - HTML Security
NEVER use formatted() with HTML and user input without escaping:
// ❌ DANGEROUS - XSS Vulnerability
String username = userInput; // User enters: "<script>alert('XSS')</script>"
String html = """
<html>
<body>
<h1>Welcome, %s!</h1>
</body>
</html>""".formatted(username);
// The script executes in the browser, potentially stealing cookies!
Always escape HTML entities:
// ✅ SAFE - Escape HTML before formatting
public static String escapeHtml(String input) {
return input
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
String username = escapeHtml(userInput);
String html = """
<html>
<body>
<h1>Welcome, %s!</h1>
</body>
</html>""".formatted(username);
Advanced Patterns
Indentation Rules
The closing """ delimiter determines indentation stripping:
// Closing delimiter indented - strips that amount
String indented = """
Hello
World
""";
// Content: "Hello\nWorld\n" (4 spaces stripped from each line)
// Closing delimiter aligned left - minimal stripping
String leftAligned = """
Hello
World
""";
// Content: " Hello\n World\n" (preserves indentation)
Essential Space and Line Continuation
Two new escape sequences provide fine-grained control:
// \s preserves trailing whitespace
String essential = """
Line with trailing space:\s
Another line
""";
// \ continues lines without newline
String continued = """
This is a very long sentence that we want to \
split in source code but keep as \
one line in the string.
""";
Relative Indentation Preserved
String structured = """
Level 1
Level 2
Level 3
Level 2
Level 1
""";
Text blocks preserve relative indentation while stripping common leading whitespace—perfect for code examples and structured data.
Key Insight
Text blocks eliminate the impedance mismatch between Java code and embedded languages (JSON, SQL, HTML), making maintenance dramatically easier. However, they are a readability feature, NOT a security feature—always use PreparedStatement for SQL and escape HTML entities for web content.
Read the Full Article
The complete guide includes:
- 8 comprehensive examples with full code and detailed output
- Security deep-dive covering SQL injection and XSS prevention patterns
- Indentation edge cases and sophisticated formatting rules
- Template system patterns for emails, invoices, and configurations
- Performance analysis demonstrating zero runtime overhead
- Text blocks vs template engines decision framework
- Complete implementation checklist for migrating existing code
- Common mistakes and how to avoid them
Read more: https://blog.9mac.dev/java-17-features-every-senior-developer-should-know-part-5-text-blocks
Clone the repository and run examples:
git clone https://github.com/dawid-swist/blog-9mac-dev-code.git
cd blog-post-examples/java/2025-10-25-java17-features-every-senior-developer-should-know
../../gradlew test --tests *part5*
All 8 examples include complete test coverage demonstrating secure and unsafe patterns.
Series Navigation:
- Part 1 - Introduction & var Keyword
- Part 2 - Records
- Part 3 - Sealed Classes
- Part 4 - Pattern Matching & Switch Expressions
- Part 5 - Text Blocks (you are here)
- Part 6 - Syntax Cheat Sheet
Top comments (0)