☕ Java

Text Blocks

Text blocks, finalized in Java 15 (after preview in Java 13–14) and delimited by triple-quotes ("""), let a multi-line string literal be written exactly as it should appear, without escaping embedded double quotes, manually inserting \n at every line break, or breaking the string into multiple concatenated lines — directly addressing the readability problems of embedding multi-line content like JSON, SQL, HTML, or any block-structured text as a traditional single-line string literal. This entry covers exactly what made traditional string literals awkward for multi-line content, text block syntax and the incidental-whitespace stripping algorithm that determines indentation, escape sequences and the new \ and \s escapes specific to text blocks, and interaction with String.format and string interpolation.

Motivation — Multi-Line Content in Single-Line String Literals

Before Java 15, a traditional string literal is necessarily a single line in source code — it cannot span multiple lines, and embedding actual multi-line content (a JSON payload, a SQL query, an HTML fragment, any text that has meaningful internal line structure) required manually inserting \n at every line break and concatenating multiple separately-quoted segments with +, with each embedded double-quote character also needing to be escaped as \". The resulting source code bore little visual resemblance to the actual content being represented — a developer reading a Java string literal containing an escaped, concatenated, multi-line SQL query had to mentally reconstruct what the actual query looked like by parsing through the escape sequences and line-continuation +'s, rather than simply reading the query as it would actually appear when executed. This mattered most for exactly the kinds of content developers most frequently need to embed directly in source — JSON test fixtures, SQL statements, HTML templates, code-generation templates, multi-line regex patterns with explanatory structure — where the embedded content's own internal formatting (indentation, line breaks) carries real meaning that a single escaped line obscures. Many projects worked around this by loading such content from separate resource files instead of inline string literals, which solved the readability problem but at the cost of splitting logically related content (the query and the code that uses it) across multiple files purely as a workaround for a string literal limitation. Java 15 (JEP 378, after preview in 13 and 14) introduced text blocks specifically to close this gap: content delimited by triple-double-quotes (""") can span multiple lines exactly as written, with embedded double quotes generally not requiring escaping (since the triple-quote delimiter is what's actually ambiguous, not a single quote) and line breaks in the source becoming line breaks in the resulting string value directly, with no \n needed at all.
Java
// ── Before Java 15 — multi-line JSON squeezed into escaped concatenation
String jsonOld = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"age\": 30,\n" +
    "  \"city\": \"Wonderland\"\n" +
    "}";
// Source code bears little visual resemblance to the actual JSON it represents —
// reader must mentally undo the escaping and concatenation to see the real content

// ── Java 15+ — text block, content written exactly as it should appear ─
String json = """
    {
      "name": "Alice",
      "age": 30,
      "city": "Wonderland"
    }
    """;
// Embedded double quotes need NO escaping at all (only the triple-quote delimiter
// itself is special) — and the source IS the content, line breaks included

// ── SQL example — same dramatic readability improvement ────────────────
String sqlOld = "SELECT id, name, email\n" +
    "FROM users\n" +
    "WHERE active = true\n" +
    "ORDER BY name";

String sql = """
    SELECT id, name, email
    FROM users
    WHERE active = true
    ORDER BY name
    """;

Incidental Whitespace Stripping — How Indentation Is Determined

A text block's content is written indented to match the surrounding source code's indentation level, for ordinary code-readability reasons, but that source-level indentation is not necessarily meant to be part of the actual string value — the compiler applies a defined algorithm to strip "incidental" leading whitespace (the indentation that exists purely because of where the text block sits in the source file) while preserving "essential" whitespace (intentional relative indentation within the content itself, such as nested JSON or HTML structure). The algorithm works by examining the minimum common leading whitespace across all non-blank lines of the text block, including the closing triple-quote delimiter's own line (whose indentation specifically establishes where the "left margin" is meant to be) — that minimum amount of leading whitespace is stripped from every line uniformly, and trailing whitespace on each line is stripped entirely (a deliberate choice to avoid invisible trailing whitespace differences causing confusing, hard-to-spot bugs or version-control diff noise). This means the position of the closing """ is not cosmetic — moving it to a different indentation level than intended actually changes the resulting string's content, which is a common source of confusion for developers new to text blocks: placing the closing delimiter flush against the left margin, rather than aligned with the intended indentation, strips less leading whitespace than intended and produces a string with extra unwanted indentation. If preserving genuinely significant trailing whitespace on a line is necessary (relatively rare, but it does occur, e.g. in some text-based test fixtures), a \s escape (added specifically for text blocks) at the end of the line preserves a single trailing space that would otherwise be stripped, since \s is not itself whitespace from the stripping algorithm's perspective until it's resolved.
Java
// ── Closing delimiter position determines the stripped margin ──────────
String text1 = """
    Hello
    World
    """;
// Closing """ aligned with "Hello"/"World" — that indentation level is the margin,
// stripped uniformly: text1 == "Hello\nWorld\n"

String text2 = """
    Hello
    World
""";
// Closing """ flush left — LESS indentation is treated as the margin, so MORE
// leading whitespace is preserved on the content lines than probably intended:
// text2 == "    Hello\n    World\n" (extra leading spaces kept!)

// ── Essential (intentional) indentation within content IS preserved ────
String html = """
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
    </ul>
    """;
// The relative indentation of <li> beneath <ul> IS essential and IS preserved —
// only the common margin shared by ALL lines (matching the closing delimiter) is stripped

// ── Trailing whitespace is stripped automatically — use \s to preserve it
String trimmedTrailing = """
    line with trailing spaces   
    next line
    """;
// Trailing spaces after "spaces" on the first line are stripped automatically,
// regardless of how many were typed — avoids invisible-whitespace diff noise

String preserved = """
    line ending with one preserved space\s
    next line
    """;
// "\s" specifically preserves exactly one trailing space that would otherwise be stripped

Escape Sequences, the \ Line-Continuation Escape, and Combining with format()

Text blocks support all the same escape sequences as ordinary string literals (\n, \t, \\, \", and so on), though \" is rarely needed in practice since a single (or even double) literal double-quote character doesn't conflict with the triple-quote delimiter and can simply be typed directly; only three consecutive double quotes would be ambiguous with the closing delimiter and require escaping at least one of them. Text blocks introduce one genuinely new escape not available in ordinary string literals: a backslash at the very end of a line (\ followed immediately by the line break) suppresses that line break entirely in the resulting string, joining that line to the next one as if no line break had been written in the source at all — useful for wrapping a single logical line of long content across multiple source lines purely for source-code readability/line-length reasons, without that source-level wrapping becoming part of the actual string value. Text blocks are themselves just String values once compiled — they support every existing String method, and in particular can be used directly with String.format(...) or formatted(...) (an instance method added in Java 15 alongside text blocks specifically to make this combination read more naturally, equivalent to String.format(this, args)), with %s and other format specifiers embedded directly within the multi-line content exactly as they would be in an ordinary single-line format string. This combination — multi-line readable template plus format substitution — is one of the more common practical uses of text blocks, particularly for SQL query templates or structured text output with variable substitution.
Java
// ── Ordinary escapes still work; \" rarely needed (only 3+ quotes are ambiguous)
String quotes = """
    She said "hello" to "everyone" in the room.
    """;
// Single/double quotes need no escaping at all — only an actual run of 3
// consecutive quotes inside the block would require escaping at least one:
String trickyQuotes = """
    The delimiter looks like \"""this if it needs escaping.
    """;

// ── The new "\" line-continuation escape — suppresses a line break ────
String longLine = """
    This is a very long logical line that we want to \
    wrap across multiple source lines purely for editor \
    readability, without those wraps becoming real newlines.
    """;
// Resulting string has NO embedded newlines from the wrapped portion above —
// it reads as one continuous line despite being written across three

// ── Combining text blocks with formatted()/String.format() ─────────────
String template = """
    Dear %s,

    Your order #%d has shipped and will arrive by %s.

    Thank you for your business.
    """;

String message = template.formatted("Alice", 12345, "Friday");
// formatted() is equivalent to String.format(template, args) — added in Java 15
// specifically so this combination reads naturally without a separate static call

String sqlTemplate = """
    SELECT * FROM orders
    WHERE customer_id = %d
    AND status = '%s'
    """;
String query = sqlTemplate.formatted(42, "SHIPPED");

Related Topics in Java 9 to 21 Features

Module System (JPMS)
The Java Platform Module System (JPMS), introduced in Java 9 under Project Jigsaw, adds a module as a new, higher-level unit of code organization above the package, allowing a JAR-like artifact to explicitly declare which packages it exports for use by other modules, which modules it depends on, and which packages it keeps entirely internal and inaccessible from outside — closing a long-standing gap in the classpath model where every public class in every JAR on the classpath was accessible to every other JAR, regardless of whether that access was intended. JPMS was also the mechanism used to decompose the JDK itself, which had grown into one monolithic rt.jar, into roughly 90 separate modules, enabling smaller custom runtime images and explicit internal-API encapsulation (notably restricting access to sun.* and other internal packages that many libraries had relied on despite warnings). This entry covers the classpath problems JPMS solves, module-info.java syntax and the requires/exports/opens directives, the distinction between the classpath and module path including automatic and unnamed modules, and jlink custom runtime images.
JShell
JShell, introduced in Java 9, is an interactive Read-Eval-Print Loop (REPL) bundled directly with the JDK, allowing Java statements, expressions, method, class, and variable declarations to be entered and evaluated one at a time at an interactive prompt, with immediate feedback — without requiring a class wrapper, a main method, explicit compilation, or a build tool, all of which Java had always required for even the smallest snippet of executable code before Java 9. This solved a long-standing friction point for learning, quick experimentation, and API exploration, where languages like Python or Ruby offered an immediate interactive prompt but Java required the full edit-compile-run cycle even to test a single line. This entry covers what problem JShell solves and how it differs from a script, core REPL usage including implicit variable creation and forward references, snippet management commands, and how to feed JShell external classes and files.
Collection Factory Methods
Collection factory methods — List.of(...), Set.of(...), and Map.of(...)/Map.ofEntries(...) — introduced in Java 9, provide a concise, standard way to create small, fixed-content, genuinely immutable collections in a single expression, replacing the previously common but verbose combination of Arrays.asList(...), Collections.unmodifiableList(...), and manual add() calls, none of which produced a collection that was both concise to write and fully immutable. This entry covers exactly what problem these factories solve compared to the older idioms, the specific immutability and null-rejection guarantees they provide (which are stricter than Collections.unmodifiableXxx), their behavior around duplicate keys/elements, and important caveats around mutability expectations and performance for very small collections.
Private Interface Methods
Private interface methods, introduced in Java 9, allow an interface to declare methods marked private — with a full method body — that are accessible only from within that same interface (from its default methods, its other private methods, and its private static methods), but completely invisible to implementing classes and to any code outside the interface. They close a code-duplication gap left after Java 8's default methods: when an interface has multiple default methods that share common logic, that shared logic previously either had to be duplicated in each default method or exposed as a public default method purely to enable reuse, polluting the interface's actual API surface with a method meant only for internal reuse. This entry covers the specific problem they solve, the distinction between private instance and private static interface methods, the rules governing what they can call and be called from, and how they interact with the broader default-method design Java established in version 8.