A Java multiline string is a string literal that spans several lines of source code, and since Java 15 we write it as a text block that starts and ends with three double quotes (“””). The compiler keeps the line breaks, removes the indentation that only comes from the code layout, and produces an ordinary String.
We use multiline strings for SQL queries, JSON and XML payloads, HTML snippets, help texts of command-line tools and expected output in tests, where the text is easier to read in its real shape than as a chain of “…\n” + pieces.
The following example stores a three-line SQL query in a text block.
String sql = """
SELECT title, artist
FROM songs
WHERE plays > 100
""";
int lineCount = (int) sql.lines().count(); // 3
boolean endsWithNewline = sql.endsWith("\n"); // true
boolean noIndent = sql.startsWith("SELECT"); // true
Notice that the eight spaces in front of each line are gone, and the string ends with a line break because the closing delimiter sits on its own line. We go through these rules, the escapes that control line breaks and spaces, the options before Java 15, and the text block methods of the String class.
| Java version | Preferred way to write a multiline string |
|---|---|
| Java 15 and later | Text blocks |
| Java 8 to Java 14 | String.join(), StringJoiner or Collectors.joining() with a line separator |
| Before Java 8 | String concatenation with + |
1. Multiline Strings with Text Blocks [Java 15+]
Text blocks were a preview feature in Java 13 (JEP 355) and Java 14 (JEP 368) and became a standard feature in Java 15 (JEP 378). Since Java 15, they need no compiler flag, so every current LTS release, including Java 17, 21 and 25, supports them.
1.1. Text Block Syntax Rules
A text block follows a few strict rules, and the compiler reports an error when one of them is broken.
- The opening “”” must be followed by a line break, so the content always starts on the next line.
- The closing “”” can stand on its own line or at the end of the last content line.
- Single and double quotes inside the text need no escape, and only three quotes in a row need one, written as \”””.
- Escape sequences such as \t and \n still work, and two new ones, \s and \ at the end of a line, control spaces and line breaks.
// does not compile: the content must start on the line after the opening delimiter
String inline = """Hello""";
String quotes = """
She said "hi" and left.
It's 'fine'.""";
boolean kept = quotes.contains("\"hi\""); // true
int quoteLines = (int) quotes.lines().count(); // 2
1.2. How Text Blocks Remove Indentation
We indent a text block to match the code around it, but that indentation is not part of the text. The JLS calls it incidental whitespace, and the compiler removes it with a simple rule. It finds the line with the fewest leading spaces, counting the closing delimiter line too, and cuts that many spaces from every line.

The position of the closing delimiter controls the indentation of the result. Moving it to the left keeps spaces in front of every line, which is handy for nested JSON or YAML that must start indented.
String flush = """
{
"title": "Yesterday"
}
""";
String indented = """
{
"title": "Yesterday"
}
""";
boolean flushStart = flush.startsWith("{"); // true
boolean indentedStart = indented.startsWith(" {"); // true
Trailing spaces at the end of each line are removed as well. Editors often hide them, so the compiler drops them to keep the result the same no matter how the file was edited. The Programmer’s Guide to Text Blocks describes the full algorithm, and JLS section 3.10.6 defines it formally.
1.3. Controlling Line Breaks and Spaces
Three small tools change the line breaks and spaces of a text block. A backslash at the end of a line joins it with the next line, \s keeps a space that would be stripped, and putting the closing delimiter on the last content line removes the final line break.
String joined = """
This sentence is long, so we split it \
over two lines in the source.""";
String padded = """
red \s
green\s
""";
boolean oneLine = joined.lines().count() == 1; // true
boolean noFinalBreak = !joined.endsWith("\n"); // true
int redLength = padded.lines().findFirst().orElse("").length(); // 6
The two new escapes join the escape sequences that every string literal knows. The compiler strips the indentation first and translates the escapes afterwards, so an escaped character is never removed as incidental whitespace.
| Escape in a text block | Result | Typical use |
|---|---|---|
| \ at the end of a line | No line break, the next line continues the same line | Long sentences or long SQL that must stay one line |
| \s | One space that is never stripped | Fixed-width columns and trailing spaces that matter |
| \””” | Three double quotes inside the text | Text that contains another text block, such as Java code in a test |
| \t, \n | A tab or a line feed | Tab-separated values, an extra line break |
| \\ | One backslash | Windows paths and regular expressions |
Text blocks always use \n as the line separator, even when the source file has Windows line endings, so the same code produces the same string on every system. When a file or a protocol needs \r\n, we convert it explicitly with text.replace(“\n”, “\r\n”).
1.4. Text Blocks Are Ordinary String Literals
A text block is only another way to write a string literal. The compiler turns it into a normal java.lang.String constant, which is interned in the string pool like any other literal, so we can pass it to a method, concatenate it, call methods on it or store it in a static final field.
String greeting = "Hello, ";
String name = """
Lokesh""";
String joined = greeting + name; // "Hello, Lokesh"
boolean sameAsLiteral = name.equals("Lokesh"); // true
int nameLength = name.length(); // 6
Nothing at runtime tells a text block apart from a quoted literal. The choice between the two is only about readability in the source code.
1.5. Tabs and Mixed Indentation
The compiler counts whitespace characters when it removes the indentation, and it does not know how wide an editor shows a tab. A tab counts as one character, the same as one space, even when the editor displays it as four or eight columns.
When one line is indented with a tab and the next one with eight spaces, the compiler sees an indentation of 1 and 8. It removes one character from each line, so the second line keeps seven spaces, although both lines looked aligned in the editor. The method stripIndent() applies the same rule at runtime, so we can show the effect with an ordinary string.
String mixed = "\tA\n B";
String result = mixed.stripIndent();
boolean irregular = result.equals("A\n B"); // true, 7 spaces before B
We indent text blocks with spaces only, or with tabs only, and let the IDE convert tabs to spaces. The compiler option -Xlint:text-blocks warns about inconsistent whitespace in a text block, and a formatter such as the one in IntelliJ IDEA or Eclipse keeps the indentation consistent.
1.6. String Methods for Multiline Text
Java 11 and Java 12 added methods to String that help with multiline text, and Java 15 added three more together with text blocks.
| Method | Since | What it does |
|---|---|---|
| lines() | Java 11 | Returns a Stream<String> of the lines, without line terminators |
| indent(n) | Java 12 | Adds n spaces to each line, or removes them for a negative n, and normalizes line endings |
| stripIndent() | Java 15 | Removes incidental indentation, the same way the compiler does for text blocks |
| translateEscapes() | Java 15 | Turns escape sequences such as \t written as two characters into the real characters |
| formatted(args) | Java 15 | Fills %s placeholders, like String.format() |
String help = """
Usage: player [options]
--shuffle play in random order""";
String shifted = help.indent(4); // 4 more spaces per line, ends with \n
String loaded = " line one\n line two";
String stripped = loaded.stripIndent();
boolean strippedOk = stripped.equals("line one\n line two"); // true
String escaped = "tab\\there".translateEscapes();
boolean realTab = escaped.equals("tab\there"); // true
long optionLines = help.lines().filter(l -> l.strip().startsWith("--")).count(); // 1
The stripIndent() method helps with text that comes from a file or a database at runtime, because the compiler can only strip indentation from literals in the source code. The Java String tutorial shows more examples of lines() and indent().
1.7. When a Text Block Is the Right Choice
Text blocks pay off when the text has more than one line, or when it contains many double quotes, as JSON does. For a short single-line message, a normal literal stays shorter and clearer.
- We use a text block for SQL, JSON, XML, HTML and expected test output, where the shape of the text matters.
- We keep a normal literal for short single-line text, because the three delimiters add two lines of noise.
- We move a long text block out of a lambda or a stream pipeline into a local variable or a static final field, so the pipeline stays readable.
- We place the closing delimiter deliberately, because its position controls both the indentation and the final line break.
2. Multiline Strings Before Java 15
Before Java 15, Java had no syntax for multiline literals, so we built the string from pieces and added the line breaks ourselves. Older code bases still use these approaches, and they remain useful when the lines come from a collection.
2.1. String Concatenation
The oldest approach joins one literal per line with the + operator and ends each piece with \n.
String content = "SELECT title, artist\n" +
"FROM songs\n" +
"WHERE plays > 100\n";
String block = """
SELECT title, artist
FROM songs
WHERE plays > 100
""";
boolean sameAsBlock = content.equals(block); // true
When every piece is a literal, the compiler joins them into one constant at compile time, so the code costs nothing at runtime. When variables are involved, Java 9 and later compile + into an invokedynamic call (JEP 280) that sizes the result once, instead of the StringBuilder chain older compilers generated. The string concatenation performance article covers the loop case, where a StringBuilder is still the right choice.
2.2. String Join
Since Java 8, String.join() takes a delimiter and the lines, and puts the delimiter between them. With System.lineSeparator(), the result uses the line ending of the platform, which matters for files that Windows tools open.
String content = String.join("\n",
"SELECT title, artist",
"FROM songs",
"WHERE plays > 100");
int joinedLines = (int) content.lines().count(); // 3
boolean trailing = content.endsWith("\n"); // false
Unlike the text block, the joined string has no line break at the end, because the delimiter only goes between elements. The StringJoiner class works the same way and also accepts a prefix and a suffix.
2.3. Java Streams
When the lines are already in a List or come from a query, Collectors.joining() builds the multiline string at the end of a stream.
List<String> songs = List.of("Yesterday", "Hallelujah", "Hey");
String playlist = songs.stream()
.map(song -> "- " + song)
.collect(Collectors.joining("\n"));
boolean firstLine = playlist.startsWith("- Yesterday\n"); // true
3. Text Blocks with Variables
A text block has no syntax for embedding variables, so we put %s placeholders in it and fill them with formatted(). String templates, which would have added interpolation, were previewed in Java 21 and 22 and withdrawn in JDK 23.
String query = """
SELECT title
FROM songs
WHERE artist = '%s'
""".formatted("Beatles");
boolean filled = query.contains("'Beatles'"); // true
For SQL, this snippet only shows the syntax. Real queries pass values as parameters of a PreparedStatement, so user input can never change the query. The text block formatting article covers formatted(), String.format(), MessageFormat and named placeholders in detail.
4. Multiline Strings in Tests and Repositories
A data access class in a music app keeps its SQL next to the method that runs it. As a text block, the query looks the same in the code review as in the database console, and a parameter placeholder keeps it safe.
String topSongs = """
SELECT title, artist, plays
FROM songs
WHERE plays > ?
ORDER BY plays DESC
""";
boolean usesParameter = topSongs.contains("plays > ?"); // true
Tests profit even more. A test for the table printer from the align text in columns tutorial can compare its output with a text block that shows the expected table in its real shape, so a failing test shows which line differs.
String expected = """
| Title | Plays |
|:------|------:|
| Hey | 75 |""";
String actual = String.join("\n", "| Title | Plays |", "|:------|------:|", "| Hey | 75 |");
boolean matches = expected.equals(actual); // true
5. Java Multiline String FAQs
Line endings, trailing newlines and Java 8 code bases cause most of the questions once text blocks are in use.
5.1. Does Java support multiline strings?
Yes, since Java 15. A text block between “”” delimiters spans several lines and produces a normal String. Before Java 15, we joined single-line literals with +, String.join() or Collectors.joining().
5.2. How do I avoid the newline at the end of a text block?
We put the closing “”” at the end of the last content line instead of on a line of its own. If the closing delimiter must stay on its own line to control indentation, we end the last content line with a backslash.
5.3. Is a text block slower than a normal string literal?
No. The compiler turns a text block into the same string constant as an equivalent normal literal, so both end up as one object in the string constant pool.
String block = """
Hey""";
boolean sameObject = block == "Hey"; // true
5.4. Does a text block use \r\n on Windows?
No. The compiler normalizes every line ending in a text block to \n, whatever line endings the source file has. We call replace(“\n”, System.lineSeparator()) when the output must use the platform line separator.
5.5. How do I write a multiline string in Java 8?
We join the lines with String.join(System.lineSeparator(), …) or with a stream and Collectors.joining(), as in section 2. Java 8 has no text blocks, and moving to a supported LTS release such as Java 21 or 25 adds them.
6. Conclusion
Text blocks are the way to write multiline strings in Java 15 and later. The compiler removes the indentation that comes from the code layout, keeps the line breaks as \n, and the closing delimiter decides both the remaining indentation and the final line break.
The escapes \ and \s fine-tune lines and spaces, and lines(), indent() and stripIndent() process multiline text at runtime. In older code, String.join() and Collectors.joining() remain the clean options.
7. References
- JEP 378, Text Blocks
- Programmer’s Guide to Text Blocks (Java 25)
- JLS 25, Text Blocks
- String Javadoc (Java 25)
Happy Learning !!