Contents

Text blocks use triple-quote delimiters (""") and must start content on a new line after the opening delimiter. Indentation is normalized: the compiler determines the common leading whitespace from the content lines and the position of the closing """, then strips that many spaces from every line — so code indentation does not become string content. Trailing whitespace on each line is removed unless you use the \s escape to explicitly preserve it. Use \ at the end of a line to suppress the newline and join lines visually without inserting a newline in the result.

// Text blocks — triple-quoted strings, finalized in Java 15 // Leading whitespace is stripped based on the least-indented line String json = """ { "name": "Alice", "age": 30, "active": true } """; System.out.println(json); // { // "name": "Alice", // "age": 30, // "active": true // } // SQL with text block — much more readable String query = """ SELECT u.name, o.total FROM users u JOIN orders o ON o.user_id = u.id WHERE u.active = true AND o.total > 100 ORDER BY o.total DESC """; // HTML template String html = """ <div class="card"> <h2>%s</h2> <p>%s</p> </div> """.formatted("Title", "Body text"); // Incidental whitespace stripping — position of the closing """ matters // Closing """ on same indentation level removes that much leading whitespace // Explicit newline control: String noTrailingNewline = """ hello\ world"""; // backslash at end of line = no newline; "helloworld" String spacer = """ a \s b"""; // \s preserves trailing space before newline // String escape sequences still work inside text blocks String escaped = """ Tab:\there, Quote: \"quoted\" """;

The STR template processor (JEP 430, preview in Java 21) used \{expression} syntax for inline interpolation. The RAW processor gave access to the StringTemplate object — the fragments and values — before processing. The FMT processor combined interpolation with printf-style format specifiers. However, string templates were redesigned after their preview period; the STR and FMT processors were removed in Java 23. Do not use them in production code without understanding the stability implications.

// String Templates — Preview in Java 21 (JEP 430) // NOTE: Withdrawn after preview, redesign ongoing — not in Java 23+ // Only available with --enable-preview in Java 21/22 // STR template processor — simple string interpolation // Syntax: STR."template \{expression} text" String name = "Alice"; int age = 30; String greeting = STR."Hello, \{name}! You are \{age} years old."; System.out.println(greeting); // Hello, Alice! You are 30 years old. // Expressions (not just variables) double price = 9.99; String receipt = STR."Total: $\{price * 1.1:.2f}"; // expression with format // Multi-line template with STR String json = STR.""" { "name": "\{name}", "age": \{age}, "adult": \{age >= 18} } """; // FMT template processor — applies printf-style format specifiers import static java.util.FormatProcessor.FMT; String formatted = FMT."Price: $%,.2f\{price} | Tax: $%,.2f\{price * 0.1}"; // Key safety advantage over String.format — embedding in SQL is validated by processor // (Custom processors can reject SQL injection, validate HTML, etc.) // Unlike: "SELECT * FROM users WHERE name = '" + name + "'" // SQL injection risk! // Available processors in Java 21 preview: // STR — basic interpolation, always produces String // FMT — interpolation + printf format specifiers // RAW — produces StringTemplate object (for custom processors) String Templates (STR, FMT) are a preview feature that requires --enable-preview. The feature was withdrawn after Java 22 for redesign. Do not use in production code without understanding the implications. Use formatted() or String.format() for stable interpolation.

A template processor implements StringTemplate.Processor and receives a StringTemplate that exposes two lists: fragments() (the literal string parts between interpolations) and values() (the interpolated expressions). The processor combines them however it wants — it can HTML-escape each interpolated value, validate that SQL identifiers are safe, or route values to JDBC prepared-statement parameters. This separation of literal structure from interpolated values is what makes template processors safe: the processor, not the caller, controls how values are embedded.

// Custom template processor — implements StringTemplate.Processor // Useful for: SQL safe interpolation, HTML escaping, JSON building import java.lang.StringTemplate; // SQL injection-safe processor StringTemplate.Processor<PreparedStatement, SQLException> SQL = st -> { // st.fragments() — the literal string parts between interpolations // st.values() — the interpolated values String sql = String.join("?", st.fragments()); // replace values with ? PreparedStatement ps = connection.prepareStatement(sql); int i = 1; for (Object value : st.values()) { ps.setObject(i++, value); // parameters set safely via JDBC } return ps; }; // Usage (Java 21 preview syntax): // String userId = getInput(); // could be "'; DROP TABLE users; --" // PreparedStatement ps = SQL."SELECT * FROM users WHERE id = \{userId}"; // ps.executeQuery(); // SQL injection-safe! // HTML-escaping processor StringTemplate.Processor<String, RuntimeException> HTML = st -> { StringBuilder sb = new StringBuilder(); Iterator<String> frags = st.fragments().iterator(); for (Object value : st.values()) { sb.append(frags.next()); sb.append(escape(value.toString())); // HTML-escape each interpolated value } sb.append(frags.next()); return sb.toString(); }; // String escape(String s) — replaces < > & " ' with HTML entities // RAW processor — get access to the StringTemplate object StringTemplate template = RAW."Hello, \{name}!"; List<String> fragments = template.fragments(); // ["Hello, ", "!"] List<Object> values = template.values(); // [name]

Because string templates are still in flux, formatted() and MessageFormat are the stable production alternatives. formatted() is an instance method (Java 15+) equivalent to String.format(this, args) — use it with a text block for a clean multi-line template. MessageFormat uses numbered placeholders {0}, {1} and supports locale-sensitive formatting including plural choice patterns. Both are finalized and stable across all supported Java versions.

// formatted() — Java 15+, instance method on String, same as String.format() String name = "Bob"; int score = 92; // Clean and stable String msg1 = "Name: %s, Score: %d".formatted(name, score); // Multi-line with text block + formatted (stable combo) String json = """ { "name": "%s", "score": %d } """.formatted(name, score); // String.format() — traditional, always works String msg2 = String.format("Name: %s, Score: %d", name, score); // StringBuilder — explicit but fast String msg3 = new StringBuilder() .append("Name: ").append(name) .append(", Score: ").append(score) .toString(); // String + concatenation — compiler converts to StringBuilder internally // Fine for simple cases, not for loops String msg4 = "Name: " + name + ", Score: " + score; // MessageFormat — handles plurals, locale, choice format String pattern = "You have {0} {0,choice,0#messages|1#message|1<messages}."; System.out.println(MessageFormat.format(pattern, 0)); // You have 0 messages. System.out.println(MessageFormat.format(pattern, 1)); // You have 1 message. System.out.println(MessageFormat.format(pattern, 5)); // You have 5 messages. For stable, production-safe string interpolation, use text blocks (Java 15+) combined with formatted(). This combination provides multi-line templates with values plugged in, without any preview feature risk.

For SQL, always use PreparedStatement parameters for user-supplied values — never string concatenation or format substitution for values (only for table/column names, and only from trusted sources). For HTML, use a templating library that escapes values. For log messages and diagnostic output, formatted() or String.format() are straightforward and safe. Await a stable string templates release before adopting template processors in production code.

// Pattern 1: Build a JSON payload with text block + formatted (production-safe) String buildUserJson(String name, int age, String email) { return """ { "name": "%s", "age": %d, "email": "%s" } """.formatted( name.replace("\"", "\\\""), // escape quotes in name age, email.replace("\"", "\\\"") ); } // Pattern 2: Multi-line SQL with named parameters (using simple replacement) String buildQuery(String table, int limit) { return """ SELECT * FROM %s WHERE active = true LIMIT %d """.formatted(table, limit); // Use PreparedStatement parameters for user input — never string format for values! } // Pattern 3: Email template String buildEmail(String recipientName, String orderId, double total) { return """ Dear %s, Your order #%s has been confirmed. Order total: $%.2f Thank you for shopping with us! """.formatted(recipientName, orderId, total); } // Pattern 4: Log message building — don't format if level is disabled private static final Logger log = Logger.getLogger(...); // BAD: "Hello " + heavyCompute() — always evaluates even if log.FINE is off // GOOD: use lambda supplier with isLoggable check log.finest(() -> "State: " + computeExpensiveState());