Text Blocks for Multiline Strings in Java

Comprehensive guide to using text blocks (introduced in Java 13, standardized in Java 15) for cleaner multiline string handling.

1. Basic Text Block Syntax

Traditional vs Text Block Comparison

public class TextBlockBasics {
// Traditional approach with concatenation
public String traditionalMultiline() {
return "{\n" +
"  \"name\": \"John Doe\",\n" +
"  \"age\": 30,\n" +
"  \"city\": \"New York\"\n" +
"}";
}
// Using text blocks (Java 15+)
public String textBlockMultiline() {
return """
{
"name": "John Doe",
"age": 30,
"city": "New York"
}""";
}
// HTML example
public String traditionalHtml() {
return "<html>\n" +
"  <head>\n" +
"    <title>My Page</title>\n" +
"  </head>\n" +
"  <body>\n" +
"    <h1>Welcome</h1>\n" +
"  </body>\n" +
"</html>";
}
public String textBlockHtml() {
return """
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>""";
}
}

Basic Rules and Syntax

public class TextBlockRules {
public void demonstrateBasicRules() {
// 1. Opening delimiter must be followed by line terminator
String valid = """
This is a valid text block""";
// 2. Content starts on next line after opening delimiter
String content = """
Line 1
Line 2
Line 3""";
System.out.println("Valid: " + valid);
System.out.println("Content:\n" + content);
}
public void commonMistakes() {
// ❌ Invalid - content on same line as opening delimiter
// String invalid = """Text on same line""";
// ❌ Invalid - no line terminator after opening delimiter  
// String alsoInvalid = """First line
//                       Second line""";
// ✅ Valid - empty text block
String empty = """
""";
// ✅ Valid - single line text block
String singleLine = """
Single line""";
}
}

2. Indentation and Formatting

Incidental White Space

public class TextBlockIndentation {
public void demonstrateIndentation() {
// The closing delimiter determines incidental white space
String perfectlyAligned = """
{
"name": "John",
"age": 30
}""";  // Closing delimiter sets baseline
String withExtraIndent = """
{
"name": "John", 
"age": 30
}""";  // Common indentation is removed
System.out.println("Aligned:\n" + perfectlyAligned);
System.out.println("With extra indent:\n" + withExtraIndent);
}
public void mixedIndentation() {
// Text block preserves relative indentation
String mixed = """
public class Example {
public void method() {
if (condition) {
doSomething();
}
}
}""";
System.out.println("Mixed indentation:\n" + mixed);
}
public void trailingSpaces() {
// Trailing spaces are preserved unless line is empty
String withTrailingSpaces = """
Line 1    \s
Line 2
Line 3   """;
System.out.println("With trailing spaces:");
System.out.println("'" + withTrailingSpaces + "'");
}
}

Controlling Indentation

public class IndentationControl {
public void alignWithCode() {
String json = """
{
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York"
}
}""";
String sql = """
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC""";
System.out.println("JSON:\n" + json);
System.out.println("SQL:\n" + sql);
}
public void preserveIndentation() {
// Use \s to preserve trailing spaces
String withPreservedSpaces = """
Line 1\s
Line 2   \s
Line 3""";
System.out.println("Preserved spaces:");
for (String line : withPreservedSpaces.split("\n")) {
System.out.println("'" + line + "'");
}
}
public void minimalIndentation() {
// Closing delimiter on same line to minimize indentation
String minimal = """
{
"compact": "format",
"saves": "space"
}""";  // Closing delimiter here
String traditional = """
{
"traditional": "format",
"more": "indentation"
}
""";  // Closing delimiter on new line adds newline
System.out.println("Minimal:\n" + minimal);
System.out.println("Traditional:\n" + traditional);
}
}

3. Real-World Use Cases

JSON and XML Processing

public class JsonXmlExamples {
public String createUserJson(String name, int age, String city) {
return """
{
"user": {
"name": "%s",
"age": %d,
"city": "%s",
"timestamp": "%s"
}
}""".formatted(name, age, city, java.time.Instant.now());
}
public String createSoapEnvelope(String bodyContent) {
return """
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<authHeader>
<apiKey>12345</apiKey>
</authHeader>
</soap:Header>
<soap:Body>
%s
</soap:Body>
</soap:Envelope>""".formatted(bodyContent);
}
public String createConfigXml() {
return """
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<database>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<username>admin</username>
<password>secret</password>
</database>
<logging>
<level>INFO</level>
<file>/var/log/app.log</file>
</logging>
</configuration>""";
}
}

SQL Queries

public class SqlQueryExamples {
public String createUserQuery() {
return """
INSERT INTO users (
username,
email,
password_hash,
created_at,
updated_at
) VALUES (?, ?, ?, NOW(), NOW())""";
}
public String complexJoinQuery() {
return """
SELECT 
u.id,
u.username,
p.title,
c.name as category_name,
COUNT(l.id) as like_count
FROM users u
JOIN posts p ON u.id = p.user_id
LEFT JOIN categories c ON p.category_id = c.id
LEFT JOIN likes l ON p.id = l.post_id
WHERE u.active = true
AND p.published_at >= ?
GROUP BY u.id, u.username, p.title, c.name
ORDER BY like_count DESC
LIMIT 100""";
}
public String dynamicWhereClause(boolean filterByDate, boolean filterByStatus) {
String baseQuery = """
SELECT *
FROM orders
WHERE 1=1""";
if (filterByDate) {
baseQuery += """
AND order_date >= ?""";
}
if (filterByStatus) {
baseQuery += """
AND status = ?""";
}
return baseQuery + """
ORDER BY order_date DESC""";
}
}

HTML Templates

public class HtmlTemplateExamples {
public String createEmailTemplate(String userName, String message) {
return """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.container { max-width: 600px; margin: 0 auto; }
.header { background: #f8f9fa; padding: 20px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Hello, %s!</h1>
</div>
<div class="content">
<p>%s</p>
<p>Thank you for using our service.</p>
</div>
</div>
</body>
</html>""".formatted(userName, message);
}
public String createErrorPage(int statusCode, String errorMessage) {
return """
<!DOCTYPE html>
<html>
<head>
<title>Error %d</title>
<meta charset="UTF-8">
<style>
body { 
font-family: Arial, sans-serif; 
text-align: center;
padding: 50px;
}
.error-code { 
font-size: 72px; 
color: #dc3545;
}
</style>
</head>
<body>
<div class="error-code">%d</div>
<h1>Oops! Something went wrong</h1>
<p>%s</p>
<a href="/">Return to Homepage</a>
</body>
</html>""".formatted(statusCode, statusCode, errorMessage);
}
}

4. String Interpolation and Formatting

Using Formatted Method

public class TextBlockFormatting {
public String createGreeting(String name, String city, int temperature) {
return """
Hello %s!
Welcome to %s. The current temperature is %d°C.
Best regards,
Weather Service
""".formatted(name, city, temperature);
}
public String createProductEmail(String productName, double price, 
int quantity, String customerName) {
return """
Dear %s,
Thank you for your order!
Product: %s
Quantity: %d
Price: $%.2f
Total: $%.2f
Your order will be shipped within 2-3 business days.
Sincerely,
The Store Team
""".formatted(customerName, productName, quantity, price, price * quantity);
}
}

Advanced Formatting

public class AdvancedFormatting {
public String createReport(String title, List<String> items, 
LocalDate reportDate, double total) {
StringBuilder itemsBuilder = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
itemsBuilder.append("""
%d. %s
""".formatted(i + 1, items.get(i)));
}
return """
REPORT: %s
Date: %s
Items:
%s
Total: $%.2f
Generated on: %s
""".formatted(
title.toUpperCase(),
reportDate.format(java.time.format.DateTimeFormatter.ISO_DATE),
itemsBuilder.toString(),
total,
java.time.LocalDateTime.now()
);
}
public String createTable(List<String> headers, List<List<String>> rows) {
StringBuilder table = new StringBuilder();
// Create header
table.append("""
| %s |
| %s |
""".formatted(
String.join(" | ", headers),
String.join(" | ", headers.stream().map(h -> "---").toList())
));
// Create rows
for (List<String> row : rows) {
table.append("""
| %s |
""".formatted(String.join(" | ", row)));
}
return table.toString();
}
}

5. Escape Sequences and Special Characters

Escape Sequences in Text Blocks

public class EscapeSequences {
public void demonstrateEscapes() {
// Traditional escape sequences work
String withEscapes = """
Line 1\nLine 2\tTabbed\nLine 3 with \"quotes\"""";
System.out.println("With escapes:");
System.out.println(withEscapes);
}
public void lineTerminatorEscapes() {
// Suppress line terminators with \
String singleLine = """
This is all \
one single \
line""";
// Space escape sequence
String withTrailingSpace = """
Line with trailing space\s""";
System.out.println("Single line: " + singleLine);
System.out.println("With trailing space: '" + withTrailingSpace + "'");
}
public void specialCases() {
// Three quotes in content
String withQuotes = """
This contains ""\" three quotes""";
// Backslash at end of line
String withBackslash = """
Line ending with backslash \\
Next line""";
System.out.println("With three quotes: " + withQuotes);
System.out.println("With backslash: " + withBackslash);
}
}

Handling Special Scenarios

public class SpecialScenarios {
public void emptyLinesAndSpaces() {
// Empty lines are preserved
String withEmptyLines = """
First line
Third line after empty line
""";
// Mixed spaces and tabs
String mixedWhitespace = """
Line 1
Line 2 with spaces
\tLine 3 with tab
""";
System.out.println("Empty lines:");
System.out.println(withEmptyLines);
System.out.println("Mixed whitespace:");
System.out.println(mixedWhitespace);
}
public void veryLongLines() {
// Text blocks handle long lines naturally
String longDescription = """
This is a very long description that would normally require 
concatenation or special handling in traditional strings, but 
with text blocks we can write naturally without worrying about 
line length limitations in the source code.""";
System.out.println("Long description: " + longDescription);
}
}

6. Performance Considerations

Performance Comparison

public class TextBlockPerformance {
// Traditional concatenation (creates multiple string objects)
public String traditionalConcat() {
return "Line 1\n" +
"Line 2\n" +
"Line 3\n" +
"Line 4\n" +
"Line 5";
}
// String.join approach
public String stringJoin() {
return String.join("\n",
"Line 1",
"Line 2", 
"Line 3",
"Line 4",
"Line 5"
);
}
// Text block (compile-time constant)
public String textBlock() {
return """
Line 1
Line 2
Line 3
Line 4
Line 5""";
}
// StringBuilder for dynamic content
public String stringBuilderApproach() {
StringBuilder sb = new StringBuilder();
sb.append("Line 1\n");
sb.append("Line 2\n");
sb.append("Line 3\n");
sb.append("Line 4\n");
sb.append("Line 5");
return sb.toString();
}
}

Memory Efficiency

public class MemoryEfficiency {
public void demonstrateStringPool() {
// Text blocks are compile-time constants
String tb1 = """
Multi-line
text block""";
String tb2 = """
Multi-line
text block""";
// These reference the same string in the pool
System.out.println("Text blocks equal: " + (tb1 == tb2));
System.out.println("Text blocks content equal: " + tb1.equals(tb2));
}
public void largeTextBlocks() {
// Large text blocks are efficient
String largeTemplate = """
%s
This is a large template that might be used for:
- Email templates
- HTML pages  
- Configuration files
- SQL queries
- JSON/XML documents
The text block makes it easy to maintain and read
without performance overhead.
Generated at: %s
""";
String filled = largeTemplate.formatted("IMPORTANT NOTICE", 
java.time.LocalDateTime.now());
System.out.println(filled);
}
}

7. Best Practices and Patterns

Code Organization

public class TextBlockBestPractices {
// Store templates as constants
private static final String SQL_INSERT_USER = """
INSERT INTO users (
username,
email,
created_at
) VALUES (?, ?, ?)""";
private static final String JSON_RESPONSE_TEMPLATE = """
{
"status": "%s",
"message": "%s",
"timestamp": "%s"
}""";
private static final String EMAIL_TEMPLATE = """
Subject: %s
Dear %s,
%s
Best regards,
%s
""";
public String buildJsonResponse(String status, String message) {
return JSON_RESPONSE_TEMPLATE.formatted(
status,
message,
java.time.Instant.now().toString()
);
}
public String buildEmail(String subject, String recipient, 
String body, String sender) {
return EMAIL_TEMPLATE.formatted(subject, recipient, body, sender);
}
}

Maintenance and Readability

public class MaintenanceExamples {
// Good: Clear structure and alignment
public String wellStructuredQuery() {
return """
SELECT 
u.id,
u.username,
COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
WHERE u.active = true
AND p.created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY u.id, u.username
HAVING COUNT(p.id) > 5
ORDER BY post_count DESC""";
}
// Good: Proper indentation for nested structures
public String wellStructuredJson() {
return """
{
"user": {
"profile": {
"name": "John Doe",
"preferences": {
"theme": "dark",
"notifications": true
}
}
}
}""";
}
// Avoid: Inconsistent indentation
public String poorlyStructured() {
return """
{
"user": {
"name": "John",
"age": 30
}
}""";  // Hard to read and maintain
}
}

8. Migration from Traditional Strings

Before and After Examples

public class MigrationExamples {
// Before: Traditional approach
public String oldStyleJson() {
return "{\n" +
"  \"firstName\": \"John\",\n" + 
"  \"lastName\": \"Doe\",\n" +
"  \"age\": 30,\n" +
"  \"address\": {\n" +
"    \"street\": \"123 Main St\",\n" +
"    \"city\": \"New York\"\n" +
"  }\n" +
"}";
}
// After: Text block
public String newStyleJson() {
return """
{
"firstName": "John",
"lastName": "Doe", 
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York"
}
}""";
}
// Before: Complex SQL
public String oldStyleSql() {
return "SELECT \n" +
"  u.id,\n" +
"  u.username,\n" + 
"  p.title,\n" +
"  p.content\n" +
"FROM users u\n" +
"JOIN posts p ON u.id = p.user_id\n" +
"WHERE u.active = true\n" +
"  AND p.published = true\n" +
"ORDER BY p.created_at DESC";
}
// After: Text block SQL  
public String newStyleSql() {
return """
SELECT 
u.id,
u.username,
p.title, 
p.content
FROM users u
JOIN posts p ON u.id = p.user_id
WHERE u.active = true
AND p.published = true
ORDER BY p.created_at DESC""";
}
}

Key Benefits Summary

FeatureBenefitExample
ReadabilityClean, natural formattingJSON, SQL, HTML
MaintainabilityEasy to modify multiline contentNo concatenation operators
PerformanceCompile-time constantsString pool optimization
ProductivityLess boilerplate codeFewer escape sequences
AlignmentPreserves code formattingConsistent indentation

Text blocks significantly improve the developer experience when working with multiline strings, making code more readable and maintainable while providing performance benefits through compile-time optimization.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper