Java Text Block Indentation: How to Control It
java text block indentation: Learn how Java text block indentation works, how to control it with the backslash escape and stripIndent(), and avoid common pitfalls.
When you write a Java text block, the compiler removes indentation that is considered incidental. This behavior is useful for keeping code readable, but it can also produce unexpected output if you do not understand the rules. Java text block indentation is determined by the least-indented line in the block, and the closing delimiter also plays a role. This article explains exactly how the stripping works and how to control it.
The Problem: Unwanted Indentation in Multi-line Strings
Before text blocks, writing a multi-line string in Java meant either concatenating strings with + or using StringBuilder. Both approaches are verbose and hard to read when the string contains formatting such as JSON, SQL, or HTML. Text blocks solve that by allowing a literal multi-line string, but they introduce a new question: how much indentation ends up in the actual string value?
Consider a simple text block:
String json = """ { "name": "Alice", "age": 30 } """;
If you print json, you might expect the leading spaces on each line to be part of the string. However, Java strips the common indentation across all lines. The result is:
{
"name": "Alice",
"age": 30
}
The closing delimiter's position matters. The compiler uses the indentation of the closing delimiter as the baseline for stripping. In the example above, the closing delimiter is indented with four spaces, and all content lines are also indented with four spaces. The compiler removes those four spaces from each line.
How Text Blocks Handle Indentation
The stripping algorithm works as follows:
- Determine the minimum indentation across all non-blank lines, including the line containing the closing delimiter.
- Remove that minimum indentation from every line.
- If a line is blank, it is not considered for the minimum, but it still gets the stripped indentation removed.
The closing delimiter is the last line of the text block. Its indentation sets the baseline. If you place the closing delimiter at the left margin, the baseline is zero, and no indentation is stripped. That can lead to content that is not aligned with the surrounding code.
For example:
String sql = """ SELECT * FROM users WHERE id = 1 """;
The closing delimiter is at column 0, so the minimum indentation is 0. The resulting string retains the four spaces on each line. If you want to strip those spaces, you must indent the closing delimiter to the same level as the content.
This behavior is consistent with the design goal of text blocks: the indentation in the source code should not leak into the runtime string unless you intentionally want it there.
Controlling Line Breaks with the Backslash Escape
Text blocks automatically append a line terminator after each line, including the last one. If you want to avoid a newline at the end of a line or join lines, you can use the \ escape at the end of a line. This suppresses the newline that would otherwise be inserted.
String html = """ <p>Hello, \ world!</p> """;
The resulting string is:
<p>Hello, world!</p>
The \ must be the last character on the line, and it removes the newline and any leading whitespace on the next line. This is useful for building long lines without concatenation.
Note that the backslash escape only affects the newline. It does not affect indentation stripping. The indentation is still removed based on the common prefix, so the spaces before the \ are stripped as usual.
Using stripIndent() and Custom Indent
In addition to the compile-time stripping, Java provides the String.stripIndent() method, which removes incidental whitespace from a string that may not have been created from a text block. This method uses the same algorithm as the compiler: it finds the minimum indentation and removes it from every line.
String raw = " line1\n line2\n line3"; String stripped = raw.stripIndent(); // stripped = "line1\n line2\nline3"
stripIndent() is useful when you receive a string from an external source and want to normalize its indentation. It also works on text blocks, but the compiler already strips indentation, so calling stripIndent() on a text block usually has no effect unless the string was constructed dynamically.
For more control, you can use indent() to add or remove spaces. The indent(int n) method adds n spaces to the beginning of each line if n is positive, or removes up to -n spaces if negative. This is not the same as stripIndent() because it does not compute a common minimum; it directly modifies each line.
String s = "a\nb"; String indented = s.indent(2); // indented = " a\n b\n"
indent() always adds a trailing newline, even if the original string did not end with one. Keep that in mind when using it.
Common Mistakes with Mixed Indentation
A frequent error is mixing tabs and spaces. The indentation stripping algorithm treats tabs as single characters, not as a number of spaces. If some lines use tabs and others use spaces, the minimum indentation calculation may not match your visual expectation. The compiler does not expand tabs; it compares the raw characters.
For example:
String bad = """ { "key": "value" } """;
The first line starts with two tabs, the second with eight spaces, and the third with two tabs. The minimum indentation is the two tabs on the first and third lines, but the second line has spaces, so the stripping removes two tabs from the first and third lines, and two spaces from the second line? Actually, the algorithm compares character by character. Since tabs and spaces are different characters, the common prefix is empty. The result is that no indentation is stripped, and the string contains the literal tabs and spaces. This can lead to inconsistent output.
To avoid this, always use spaces for indentation in text blocks. Most Java style guides recommend spaces, and modern IDEs can be configured to replace tabs with spaces.
Another mistake is placing the closing delimiter at a different indentation level than the content. If the closing delimiter is less indented than the content, the compiler strips the closing delimiter's indentation from all lines, which may be less than what you intended. If it is more indented, the content lines will have extra spaces that you did not expect.
Compatibility and Migration Considerations
Text blocks were introduced as a standard feature in Java 15 (JEP 378). If your project runs on an earlier version, you cannot use text blocks. For Java 15 and later, they are fully supported. When migrating existing code that uses string concatenation, text blocks can simplify the code, but you must verify the resulting string values carefully, especially if the original strings contained leading or trailing whitespace.
The stripIndent() method was also added in Java 15. If you need to support older versions, you can implement a similar utility method, but it is not part of the standard library before that version.
One operational consideration is that text blocks are compile-time constants. This means they are resolved at compile time and stored in the constant pool. This is generally efficient, but it also means that any indentation stripping is done at compile time, not at runtime. There is no runtime cost for the stripping itself. If you need to dynamically adjust indentation at runtime, use indent() or stripIndent() on a string variable.
When using text blocks in code that is processed by tools like static analyzers or formatters, be aware that the indentation of the text block content is part of the source code and can affect the tool's behavior. Most modern IDEs handle text blocks correctly, but older tools may not understand the syntax and could reformat the block incorrectly.
Finally, remember that the backslash escape for suppressing newlines is a text-block-specific feature. It is not available in regular string literals. If you are migrating a regular string that uses \n escapes, you may need to adjust the syntax when converting to a text block.