☕ Java
Text Blocks
Learn Java text blocks: triple-quote syntax, how indentation stripping works, \s and line-continuation escapes, and practical SQL, JSON and HTML patterns.
Text Block Syntax and the Indentation Algorithm
A text block is delimited by three double-quote characters on each end. The opening delimiter (three double quotes) must be followed immediately by a newline — no content can appear on the opening delimiter line. The closing delimiter position controls indentation stripping: the JVM finds the leftmost non-whitespace character of any content line or the column of the closing delimiter (whichever is further left) and strips that many characters of leading whitespace from every line. This is called the re-indentation algorithm.
The purpose of re-indentation is to allow the text block's content to be visually aligned with the surrounding code without the alignment appearing in the actual string value. A text block indented sixteen spaces because it appears inside a deeply nested method should not produce a string that starts with sixteen spaces — the programmer wanted the indentation for readability, not as content. The algorithm strips the "incidental" whitespace introduced purely for source code alignment.
The closing delimiter's position is significant: placing it at the end of the last content line includes no trailing newline. Placing it on its own line at the indentation level includes a trailing newline. This gives precise control over whether the produced string ends with a newline character, which matters for SQL queries, HTTP bodies, and any context where trailing newlines affect semantics.
Java
// ── Basic text block ─────────────────────────────────────────────────
String json = """
{
"name": "Alice",
"age": 30,
"active": true
}
""";
// Equivalent to:
// "{\n \"name\": \"Alice\",\n \"age\": 30,\n \"active\": true\n}\n"
// ── Indentation stripping — closing delimiter controls indent ──────────
// The closing """ is at column 8 (8 spaces from left margin)
// Java strips 8 spaces from EVERY line's leading whitespace
String sql = """
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY name
""";
// Leading 8 spaces stripped — result has NO leading spaces:
// "SELECT id, name, email\nFROM users\nWHERE active = true\nORDER BY name\n"
// ── Closing delimiter position controls trailing newline ───────────────
// Closing """ on its own line → string ENDS with \n
String withNewline = """
Hello
World
"""; // ← """ on its own line: ends with \n
// Closing """ on last content line → NO trailing \n
String noNewline = """
Hello
World"""; // ← """ on content line: no trailing \n
System.out.println(withNewline.endsWith("\n")); // true
System.out.println(noNewline.endsWith("\n")); // false
// ── Alignment of closing delimiter affects content indentation ─────────
// Closing """ further left than content → extra spaces in result
String extraIndent = """
line one
line two
"""; // closing at column 8, content at column 12 → 4 leading spaces per line
// " line one\n line two\n"Text Block Escape Sequences
Text blocks support all standard Java escape sequences (\n, \t, \\, \", etc.) plus two new escape sequences introduced specifically for text blocks. The line terminator escape \<newline> suppresses the actual newline at the end of a source line, allowing long logical lines to be physically split across multiple source lines without the newline appearing in the string value. This is useful for long SQL statements or long prose where breaking the visual line for readability should not produce a multi-line string.
The space escape \s inserts a single space character that is significant to the re-indentation algorithm. Trailing whitespace on text block lines is normally stripped — the compiler removes spaces and tabs that appear at the end of content lines. This stripping is usually the right behaviour (trailing whitespace in source is almost never intentional content), but occasionally trailing spaces are meaningful (in test expectations, in protocol messages). The \s escape preserves the space at a specific position by replacing it with a non-stripped marker that produces a space in the final string.
Normal double quotes do not need escaping inside text blocks, which is one of their main readability advantages for JSON, HTML, and SQL content. The exception is a run of three or more consecutive double quotes: three unescaped quotes in a row close the text block. To include them in the content, escape at least one quote in each run of three, for example \""" or ""\".
Java
// ── Standard escapes work inside text blocks ─────────────────────────
String withTab = """
Column1\tColumn2\tColumn3
value1\tvalue2\tvalue3
""";
// ── \<newline> — suppress line break in the source ────────────────────
// This SQL appears on three source lines but is one logical line:
String sql = """
SELECT id, name, email FROM users \
WHERE active = true AND age > 18 \
ORDER BY name LIMIT 100
""";
// Result: "SELECT id, name, email FROM users WHERE active = true AND age > 18 ORDER BY name LIMIT 100\n"
// (The \ at end of each line suppresses the newline)
// ── \s — preserve trailing space ──────────────────────────────────────
// Without \s, trailing spaces are stripped:
String noTrail = """
line one
line two
""";
// "line one\nline two\n" — trailing spaces removed
// With \s, space before \s is preserved:
String withTrail = """
line one \s
line two \s
""";
// "line one \nline two \n" — five trailing spaces preserved on each line
// ── Double quotes do NOT need escaping inside text blocks ─────────────
String html = """
<a href="https://example.com" class="link">
Click "here" to continue
</a>
""";
// No escaping needed for " — major readability improvement over regular strings
// ── Triple quote inside text block — escape one quote in the run ──────
// Option 1: escape the first quote
String code = """
String msg = \"""Hello\""";
""";
// Option 2: escape the last quote of each run
String code2 = """
String msg = ""\"Hello""\";
""";
// Both produce: String msg = """Hello""";
// ── Before vs after text blocks — JSON comparison ─────────────────────
// Before (Java 14 and earlier):
String jsonOld = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30,\n" +
" \"active\": true\n" +
"}";
// After (Java 15+):
String jsonNew = """
{
"name": "Alice",
"age": 30,
"active": true
}
""";Practical Patterns — SQL, HTML, JSON
Text blocks transform the ergonomics of embedding structured text in Java code. SQL queries become readable multi-line statements that mirror how a database developer would write them. HTML templates look like HTML. JSON payloads look like JSON. This readability improvement is not cosmetic — it reduces errors caused by misreading or mistyping escaped string content, and it makes the code self-documenting.
The formatted() method (instance method on String since Java 15) works naturally with text blocks to produce parameterised text. Calling textBlock.formatted(arg1, arg2) applies printf-style substitution and returns a new String with the arguments substituted. This is cleaner than String.format(textBlock, args) but semantically identical.
Text blocks are interned like regular string literals — they participate in the string pool. A text block that appears multiple times in code with the same content resolves to the same pool entry. Text blocks are also String objects in every other respect: they can be assigned to String variables, passed to String parameters, and used wherever String is expected.
Java
// ── SQL with text block ──────────────────────────────────────────────
String findActiveUsers = """
SELECT
u.id,
u.name,
u.email,
COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true
AND u.created_at > :cutoffDate
GROUP BY u.id, u.name, u.email
HAVING COUNT(o.id) > :minOrders
ORDER BY order_count DESC
""";
// ── HTML template ─────────────────────────────────────────────────────
String emailTemplate = """
<!DOCTYPE html>
<html>
<body>
<h1>Welcome, %s!</h1>
<p>Your account has been created.</p>
<p>Click <a href="%s">here</a> to verify.</p>
</body>
</html>
""";
// ── formatted() — substitute values into text block ───────────────────
String email = emailTemplate.formatted("Alice", "https://example.com/verify/abc123");
// ── JSON payload ──────────────────────────────────────────────────────
String createUserBody = """
{
"username": "%s",
"email": "%s",
"role": "USER",
"active": true
}
""".formatted(username, email);
// ── YAML configuration ────────────────────────────────────────────────
String yamlConfig = """
server:
port: %d
host: %s
database:
url: %s
pool-size: %d
""".formatted(port, host, dbUrl, poolSize);
// ── Test assertions — expected multi-line output ───────────────────────
@Test
void formatReport_producesExpectedLayout() {
String expected = """
ID Name Score
-- ---- -----
1 Alice 95
2 Bob 87
""";
String actual = formatter.formatReport(testData);
assertThat(actual).isEqualTo(expected);
}
// ── stripIndent() and translateEscapes() — programmatic manipulation ──
// stripIndent() applies the text block re-indentation algorithm to a runtime string
String indented = " line1\n line2\n line3\n";
String stripped = indented.stripIndent();
System.out.println(stripped); // "line1\nline2\nline3\n"
// translateEscapes() interprets \n, \t etc. in a runtime string
String withEscapes = "line1\\nline2\\nline3";
System.out.println(withEscapes.translateEscapes()); // prints on three linesCommon Mistakes with Text Blocks
Most text block bugs come from four places. First, putting any content after the opening delimiter on the same line is a compile error — the opening """ must be followed by a line break. Second, forgetting that the line-continuation backslash joins lines exactly as written: if there is no space before the backslash, the words on either side run together. Third, using formatted() on a text block that contains a literal percent sign: every % that is not a format specifier must be written as %%, otherwise formatted() throws an exception or substitutes the wrong value. Fourth, mixing tabs and spaces for indentation. The re-indentation algorithm counts each tab or space as one character, so a block indented with a mix of the two strips unevenly and produces ragged output. Pick one and configure your IDE to use it consistently.
A related habit worth adopting: when a text block feeds a test assertion, keep the closing delimiter on its own line so the expected string ends with a newline that matches the output of the code under test, and use \s wherever trailing spaces matter, since editors and formatters often strip them silently.
Java
// ── Mistake 1: content after the opening delimiter (compile error) ───
// String bad = """Hello
// World""";
// Correct: line break right after the opening delimiter
String good = """
Hello
World""";
// ── Mistake 2: missing space before the line-continuation backslash ──
String sqlBad = """
SELECT id FROM users\
WHERE active = true
""";
// "SELECT id FROM usersWHERE active = true\n" ← words run together
String sqlGood = """
SELECT id FROM users \
WHERE active = true
""";
// "SELECT id FROM users WHERE active = true\n"
// ── Mistake 3: literal % inside a text block used with formatted() ───
// Wrong — "% c" is parsed as a format specifier and throws at runtime
// String bad = """
// Completed: %d% complete
// """.formatted(75);
// Correct — double the percent sign
String progress = """
Completed: %d%%
""".formatted(75);
// "Completed: 75%\n"
// ── Mistake 4: assuming trailing spaces survive ──────────────────────
String padded = """
Name: \s
""";
// "Name: \n" — \s keeps the final space; plain trailing spaces would be strippedRelated Topics in Strings
String Class
String is one of the most fundamental classes in Java — used in virtually every program, yet deeply misunderstood by many developers. A String represents an immutable sequence of Unicode characters. It is not a primitive type but a full class in java.lang, automatically imported into every Java file. Understanding String means understanding how it is stored in memory, why it is immutable, how the string pool works, what the difference between == and equals() means for strings, and how to use the class efficiently. This entry covers String's nature as a class, its internal representation, the critical distinction between reference equality and value equality, String's place in the type hierarchy, and the design decisions that make String behave the way it does.
String Pool
The string pool (also called the string intern pool or string constant pool) is a special memory region maintained by the JVM that stores a single copy of each unique string value. When two string literals have the same content, they refer to the same object in the pool rather than two separate objects. The pool is a flyweight pattern applied at the language level — it dramatically reduces memory consumption in applications that use many repeated string values, which is nearly every application. This entry covers how the pool works, where it lives in JVM memory, how to interact with it programmatically, the intern() method, performance implications, and when to use or avoid pool entries.
Immutable String
String immutability is the most important design decision in Java's String class. Once a String object is created, its character sequence can never change. No method on String modifies the string; every method that appears to modify returns a new String object containing the result. This design decision drives thread safety, enables the string pool, makes strings safe hash map keys, and simplifies reasoning about string values. Understanding why String is immutable, how immutability is enforced, and what the consequences of immutability are clarifies the behaviour of virtually every piece of Java code that handles strings.
Mutable String
Java provides two mutable string classes for scenarios where String's immutability would be inefficient: StringBuilder and StringBuffer. Both maintain an internal character buffer that can be modified in place — characters can be appended, inserted, deleted, and replaced without creating new objects. StringBuilder is the modern choice for single-threaded use; StringBuffer is the legacy thread-safe version with synchronised methods. This entry covers the internal buffer mechanics, the full API of both classes, performance characteristics, when to use each, thread safety implications, and the complete patterns for efficient string construction.