Java Text Block Indentation: How to Control It
Java text block indentation is stripped based on the block's minimum indentation. Learn how the compiler removes incidental whitespace, how the backslash escape joins lines, and when to use stripIndent() and indent().
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 non-blank line in the block, and the closing delimiter usually plays that 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. If it is the least-indented line, its indentation is the amount that will be stripped. 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 many leading whitespace characters from every line.
- Blank lines are not considered when computing the minimum, but leading whitespace is still removed from them.
The closing delimiter is normally written on its own line. In that common form, its indentation sets the baseline when it is the least-indented line. 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 normally include a line terminator after each content line. 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 line terminator and the leading whitespace on the next line.
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 line break. It does not change the indentation-stripping rule; the common leading whitespace is still removed, 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 leading whitespace from a string. It uses the same general rule as text blocks: it finds the minimum leading whitespace among non-blank lines and removes that amount 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, or when you build a string dynamically. On a typical text block, the compiler has already removed the common indentation, so stripIndent() often has no additional effect. It can still matter if the text block content is indented more than the minimum, or if you construct a string at runtime.
For more control, you can use indent(). 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. It does not compute a common minimum; it adjusts each line by the given amount.
String s = "a\nb"; String indented = s.indent(2); // indented = " a\n b\n"
indent() always adds a trailing line terminator, 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 stripping algorithm counts leading whitespace characters, so a tab counts as one character rather than the visual width configured in your editor. If some lines use tabs and others use spaces, the result rarely matches what the source looks like.
For example:
String bad = """ { "key": "value" } """;
The first and third lines have two leading tabs, the second line has eight leading spaces, and the closing delimiter also has two leading tabs. The minimum indentation is two whitespace characters, so two leading characters are removed from every line. The tab-indented lines lose two tabs, and the space-indented line loses two spaces:
{
"key": "value"
}
This mismatch is confusing, so use spaces consistently in text blocks. Most Java style guides recommend spaces, and most IDEs can be configured to replace tabs with spaces.
Another mistake is not aligning the closing delimiter with the least-indented content line. If the closing delimiter is less indented than the content, it supplies the minimum, so less indentation is stripped than the content's own indentation might suggest. If it is more indented than the least-indented content line, the minimum comes from the content instead. The practical rule is to align the closing delimiter with the least-indented content line.
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 block literals are compile-time constants. They are resolved at compile time and stored in the constant pool. Indentation stripping is performed by the compiler, not at runtime, so the stripping itself has no runtime cost. If you need to adjust indentation dynamically 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.