Java Multiline String: Text Blocks and Older Options

Write a Java multiline string with text blocks, control indentation, line breaks and trailing spaces, and see the String.join() options for Java 8 to 14.

A text block in source code where the leftmost content line and the closing delimiter decide how many leading spaces are removed from each line

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 versionPreferred way to write a multiline string
Java 15 and laterText blocks
Java 8 to Java 14String.join(), StringJoiner or Collectors.joining() with a line separator
Before Java 8String 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.

A text block in source code where the leftmost content line and the closing delimiter decide how many leading spaces are removed from each line
The leftmost line, or the closing delimiter if it is further left, decides how much indentation the compiler removes

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 blockResultTypical use
\ at the end of a lineNo line break, the next line continues the same lineLong sentences or long SQL that must stay one line
\sOne space that is never strippedFixed-width columns and trailing spaces that matter
\”””Three double quotes inside the textText that contains another text block, such as Java code in a test
\t, \nA tab or a line feedTab-separated values, an extra line break
\\One backslashWindows 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.

MethodSinceWhat it does
lines()Java 11Returns a Stream<String> of the lines, without line terminators
indent(n)Java 12Adds n spaces to each line, or removes them for a negative n, and normalizes line endings
stripIndent()Java 15Removes incidental indentation, the same way the compiler does for text blocks
translateEscapes()Java 15Turns escape sequences such as \t written as two characters into the real characters
formatted(args)Java 15Fills %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

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.