Does Java have support for multiline strings?

Viewed 700062

Coming from Perl, I sure am missing the "here-document" means of creating a multi-line string in source code:

$string = <<"EOF"  # create a three-line string
text
text
text
EOF

In Java, I have to have cumbersome quotes and plus signs on every line as I concatenate my multiline string from scratch.

What are some better alternatives? Define my string in a properties file?

Edit: Two answers say StringBuilder.append() is preferable to the plus notation. Could anyone elaborate as to why they think so? It doesn't look more preferable to me at all. I'm looking for a way around the fact that multiline strings are not a first-class language construct, which means I definitely don't want to replace a first-class language construct (string concatenation with plus) with method calls.

Edit: To clarify my question further, I'm not concerned about performance at all. I'm concerned about maintainability and design issues.

44 Answers

NOTE: This answer applies to Java 14 and older.

Text blocks (multiline literals) were introduced in Java 15. See this answer for details.


It sounds like you want to do a multiline literal, which does not exist in Java.

Your best alternative is going to be strings that are just +'d together. Some other options people have mentioned (StringBuilder, String.format, String.join) would only be preferable if you started with an array of strings.

Consider this:

String s = "It was the best of times, it was the worst of times,\n"
         + "it was the age of wisdom, it was the age of foolishness,\n"
         + "it was the epoch of belief, it was the epoch of incredulity,\n"
         + "it was the season of Light, it was the season of Darkness,\n"
         + "it was the spring of hope, it was the winter of despair,\n"
         + "we had everything before us, we had nothing before us";

Versus StringBuilder:

String s = new StringBuilder()
           .append("It was the best of times, it was the worst of times,\n")
           .append("it was the age of wisdom, it was the age of foolishness,\n")
           .append("it was the epoch of belief, it was the epoch of incredulity,\n")
           .append("it was the season of Light, it was the season of Darkness,\n")
           .append("it was the spring of hope, it was the winter of despair,\n")
           .append("we had everything before us, we had nothing before us")
           .toString();

Versus String.format():

String s = String.format("%s\n%s\n%s\n%s\n%s\n%s"
         , "It was the best of times, it was the worst of times,"
         , "it was the age of wisdom, it was the age of foolishness,"
         , "it was the epoch of belief, it was the epoch of incredulity,"
         , "it was the season of Light, it was the season of Darkness,"
         , "it was the spring of hope, it was the winter of despair,"
         , "we had everything before us, we had nothing before us"
);

Versus Java8 String.join():

String s = String.join("\n"
         , "It was the best of times, it was the worst of times,"
         , "it was the age of wisdom, it was the age of foolishness,"
         , "it was the epoch of belief, it was the epoch of incredulity,"
         , "it was the season of Light, it was the season of Darkness,"
         , "it was the spring of hope, it was the winter of despair,"
         , "we had everything before us, we had nothing before us"
);

If you want the newline for your particular system, you either need to use System.lineSeparator(), or you can use %n in String.format.

Another option is to put the resource in a text file, and just read the contents of that file. This would be preferable for very large strings to avoid unnecessarily bloating your class files.

Stephen Colebourne has created a proposal for adding multi-line strings in Java 7.

Also, Groovy already has support for multi-line strings.

JEP 378: Text Blocks covers this functionality and is included in JDK 15. It first appeared as JEP 355: Text Blocks (Preview) in JDK 13 and JEP 368: Text Blocks (Second Preview) in JDK 14 and can be enabled in these versions with the ––enable–preview javac option.

The syntax allows to write something like:

String s = """
           text
           text
           text
           """;

Previous to this JEP, in JDK 12, JEP 326: Raw String Literals aimed to implement a similar feature, but it was eventually withdrawn:

Please note: This was intended to be a preview language feature in JDK 12, but it was withdrawn and did not appear in JDK 12. It was superseded by Text Blocks (JEP 355) in JDK 13.

Another option may be to store long strings in an external file and read the file into a string.

Java 13 and beyond

Multiline Strings are now supported in Java via Text Blocks. In Java 13 and 14, this feature requires you to set the ––enable–preview option when building and running your project. In Java 15 and later, this option is no longer required as Text Blocks have become a standard feature. Check out the official Programmer's Guide to Text Blocks for more details.

Now, prior to Java 13, this is how you'd write a query:

List<Tuple> posts = entityManager
.createNativeQuery(
    "SELECT *\n" +
    "FROM (\n" +
    "    SELECT *,\n" +
    "           dense_rank() OVER (\n" +
    "               ORDER BY \"p.created_on\", \"p.id\"\n" +
    "           ) rank\n" +
    "    FROM (\n" +
    "        SELECT p.id AS \"p.id\",\n" +
    "               p.created_on AS \"p.created_on\",\n" +
    "               p.title AS \"p.title\",\n" +
    "               pc.id as \"pc.id\",\n" +
    "               pc.created_on AS \"pc.created_on\",\n" +
    "               pc.review AS \"pc.review\",\n" +
    "               pc.post_id AS \"pc.post_id\"\n" +
    "        FROM post p\n" +
    "        LEFT JOIN post_comment pc ON p.id = pc.post_id\n" +
    "        WHERE p.title LIKE :titlePattern\n" +
    "        ORDER BY p.created_on\n" +
    "    ) p_pc\n" +
    ") p_pc_r\n" +
    "WHERE p_pc_r.rank <= :rank\n",
    Tuple.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setParameter("rank", 5)
.getResultList();

Thanks to Java 13 Text Blocks, you can rewrite this query as follows:

List<Tuple> posts = entityManager
.createNativeQuery("""
    SELECT *
    FROM (
        SELECT *,
               dense_rank() OVER (
                   ORDER BY "p.created_on", "p.id"
               ) rank
        FROM (
            SELECT p.id AS "p.id",
                   p.created_on AS "p.created_on",
                   p.title AS "p.title",
                   pc.id as "pc.id",
                   pc.created_on AS "pc.created_on",
                   pc.review AS "pc.review",
                   pc.post_id AS "pc.post_id"
            FROM post p
            LEFT JOIN post_comment pc ON p.id = pc.post_id
            WHERE p.title LIKE :titlePattern
            ORDER BY p.created_on
        ) p_pc
    ) p_pc_r
    WHERE p_pc_r.rank <= :rank
    """,
    Tuple.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setParameter("rank", 5)
.getResultList();

Much more readable, right?

IDE support

IntelliJ IDEA provides support for transforming legacy String concatenation blocks to the new multiline String format:

IntelliJ IDEA Text Blocks support

JSON, HTML, XML

The multiline String is especially useful when writing JSON, HTML, or XML.

Consider this example using String concatenation to build a JSON string literal:

entityManager.persist(
    new Book()
    .setId(1L)
    .setIsbn("978-9730228236")
    .setProperties(
        "{" +
        "   \"title\": \"High-Performance Java Persistence\"," +
        "   \"author\": \"Vlad Mihalcea\"," +
        "   \"publisher\": \"Amazon\"," +
        "   \"price\": 44.99," +
        "   \"reviews\": [" +
        "       {" +
        "           \"reviewer\": \"Cristiano\", " +
        "           \"review\": \"Excellent book to understand Java Persistence\", " +
        "           \"date\": \"2017-11-14\", " +
        "           \"rating\": 5" +
        "       }," +
        "       {" +
        "           \"reviewer\": \"T.W\", " +
        "           \"review\": \"The best JPA ORM book out there\", " +
        "           \"date\": \"2019-01-27\", " +
        "           \"rating\": 5" +
        "       }," +
        "       {" +
        "           \"reviewer\": \"Shaikh\", " +
        "           \"review\": \"The most informative book\", " +
        "           \"date\": \"2016-12-24\", " +
        "           \"rating\": 4" +
        "       }" +
        "   ]" +
        "}"
    )
);

You can barely read the JSON due to the escaping characters and the abundance of double quotes and plus signs.

With Java Text Blocks, the JSON object can be written like this:

entityManager.persist(
    new Book()
    .setId(1L)
    .setIsbn("978-9730228236")
    .setProperties("""
        {
           "title": "High-Performance Java Persistence",
           "author": "Vlad Mihalcea",
           "publisher": "Amazon",
           "price": 44.99,
           "reviews": [
               {
                   "reviewer": "Cristiano",
                   "review": "Excellent book to understand Java Persistence",
                   "date": "2017-11-14",
                   "rating": 5
               },
               {
                   "reviewer": "T.W",
                   "review": "The best JPA ORM book out there",
                   "date": "2019-01-27",
                   "rating": 5
               },
               {
                   "reviewer": "Shaikh",
                   "review": "The most informative book",
                   "date": "2016-12-24",
                   "rating": 4
               }
           ]
        }
        """
    )
);

Ever since I used C# in 2004, I've been wanting to have this feature in Java, and now we finally have it.

If you define your strings in a properties file it'll look much worse. IIRC, it'll look like:

string:text\u000atext\u000atext\u000a

Generally it's a reasonable idea to not embed large strings in to source. You might want to load them as resources, perhaps in XML or a readable text format. The text files can be either read at runtime or compiled into Java source. If you end up placing them in the source, I suggest putting the + at the front and omitting unnecessary new lines:

final String text = ""
    +"text "
    +"text "
    +"text"
;

If you do have new lines, you might want some of join or formatting method:

final String text = join("\r\n"
    ,"text"
    ,"text"
    ,"text"
);

Pluses are converted to StringBuilder.append, except when both strings are constants so the compiler can combine them at compile time. At least, that's how it is in Sun's compiler, and I would suspect most if not all other compilers would do the same.

So:

String a="Hello";
String b="Goodbye";
String c=a+b;

normally generates exactly the same code as:

String a="Hello";
String b="Goodbye":
StringBuilder temp=new StringBuilder();
temp.append(a).append(b);
String c=temp.toString();

On the other hand:

String c="Hello"+"Goodbye";

is the same as:

String c="HelloGoodbye";

That is, there's no penalty in breaking your string literals across multiple lines with plus signs for readability.

In the IntelliJ IDE you just need to type:

""

Then position your cursor inside the quotation marks and paste your string. The IDE will expand it into multiple concatenated lines.

String newline = System.getProperty ("line.separator");
string1 + newline + string2 + newline + string3

But, the best alternative is to use String.format

String multilineString = String.format("%s\n%s\n%s\n",line1,line2,line3);

Sadly, Java does not have multi-line string literals. You either have to concatenate string literals (using + or StringBuilder being the two most common approaches to this) or read the string in from a separate file.

For large multi-line string literals I'd be inclined to use a separate file and read it in using getResourceAsStream() (a method of the Class class). This makes it easy to find the file as you don't have to worry about the current directory versus where your code was installed. It also makes packaging easier, because you can actually store the file in your jar file.

Suppose you're in a class called Foo. Just do something like this:

Reader r = new InputStreamReader(Foo.class.getResourceAsStream("filename"), "UTF-8");
String s = Utils.readAll(r);

The one other annoyance is that Java doesn't have a standard "read all of the text from this Reader into a String" method. It's pretty easy to write though:

public static String readAll(Reader input) {
    StringBuilder sb = new StringBuilder();
    char[] buffer = new char[4096];
    int charsRead;
    while ((charsRead = input.read(buffer)) >= 0) {
        sb.append(buffer, 0, charsRead);
    }
    input.close();
    return sb.toString();
}

You can concatenate your appends in a separate method like:

public static String multilineString(String... lines){
   StringBuilder sb = new StringBuilder();
   for(String s : lines){
     sb.append(s);
     sb.append ('\n');
   }
   return sb.toString();
}

Either way, prefer StringBuilder to the plus notation.

With JDK/12 early access build # 12, one can now use multiline strings in Java as follows :

String multiLine = `First line
    Second line with indentation
Third line
and so on...`; // the formatting as desired
System.out.println(multiLine);

and this results in the following output:

First line
    Second line with indentation
Third line
and so on...

Edit: Postponed to java 13

Java 13 preview:

Text Blocks Come to Java. Java 13 delivers long-awaited multiline string by Mala Gupta

With text blocks, Java 13 is making it easier for you to work with multiline string literals. You no longer need to escape the special characters in string literals or use concatenation operators for values that span multiple lines.

Text block is defined using three double quotes (""") as the opening and closing delimiters. The opening delimiter can be followed by zero or more white spaces and a line terminator.

Example:

 String s1 = """
 text
 text
 text
 """;

A quite efficient and platform independent solution would be using the system property for line separators and the StringBuilder class to build strings:

String separator = System.getProperty("line.separator");
String[] lines = {"Line 1", "Line 2" /*, ... */};

StringBuilder builder = new StringBuilder(lines[0]);
for (int i = 1; i < lines.length(); i++) {
    builder.append(separator).append(lines[i]);
}
String multiLine = builder.toString();

Define my string in a properties file?

Multiline strings aren't allowed in properties files. You can use \n in properties files, but I don't think that is much of a solution in your case.

When a long series of + are used, only one StringBuilder is created, unless the String is determined at compile time in which case no StringBuilder is used!

The only time StringBuilder is more efficient is when multiple statements are used to construct the String.

String a = "a\n";
String b = "b\n";
String c = "c\n";
String d = "d\n";

String abcd = a + b + c + d;
System.out.println(abcd);

String abcd2 = "a\n" +
        "b\n" +
        "c\n" +
        "d\n";
System.out.println(abcd2);

Note: Only one StringBuilder is created.

  Code:
   0:   ldc     #2; //String a\n
   2:   astore_1
   3:   ldc     #3; //String b\n
   5:   astore_2
   6:   ldc     #4; //String c\n
   8:   astore_3
   9:   ldc     #5; //String d\n
   11:  astore  4
   13:  new     #6; //class java/lang/StringBuilder
   16:  dup
   17:  invokespecial   #7; //Method java/lang/StringBuilder."<init>":()V
   20:  aload_1
   21:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   24:  aload_2
   25:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   28:  aload_3
   29:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   32:  aload   4
   34:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   37:  invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   40:  astore  5
   42:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   45:  aload   5
   47:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   50:  ldc     #12; //String a\nb\nc\nd\n
   52:  astore  6
   54:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   57:  aload   6
   59:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   62:  return

To clarify my question further, I'm not concerned about performance at all. I'm concerned about maintainability and design issues.

Make it as clear and simple as you can.

Java15 now supports triple-quoted strings a la Python.

One small trick. Using this I inject javascritp in a dynamically created HTML page

StringBuilder builder = new StringBuilder();

public String getString()
{
    return builder.toString();
}
private DropdownContent _(String a)
{
    builder.append(a);
    return this;
}

public String funct_showhide()
{
   return
    _("function slidedown_showHide(boxId)").
    _("{").
    _("if(!slidedown_direction[boxId])slidedown_direction[boxId] = 1;").
    _("if(!slideDownInitHeight[boxId])slideDownInitHeight[boxId] = 0;").
    _("if(slideDownInitHeight[boxId]==0)slidedown_direction[boxId]=slidedownSpeed; ").
    _("else slidedown_direction[boxId] = slidedownSpeed*-1;").
    _("slidedownContentBox = document.getElementById(boxId);").
    _("var subDivs = slidedownContentBox.getElementsByTagName('DIV');").
    _("for(var no=0;no<subDivs.length;no++){").
    _(" if(subDivs[no].className=='dhtmlgoodies_content')slidedownContent = subDivs[no];").
    _("}").
    _("contentHeight = slidedownContent.offsetHeight;").
    _("slidedownContentBox.style.visibility='visible';").
    _("slidedownActive = true;").
    _("slidedown_showHide_start(slidedownContentBox,slidedownContent);").
    _("}").getString();

}

Two answers to this question:

  1. In you want to stick to pure Java, with Java 14 being released in March 2020, you can leverage the JEP 368 - Text Blocks, in Second Preview mode. Actually the feature is in preview mode in other releases (at least 13 has it). I created and example set here.
  2. While this feature is useful, it can be easily abused. Remember that Java requires compilation - having large character arrays in your code can be an easy way to shoot yourself in the leg (if you want a quick change, you will need recompilation - that toolset might not be available to the guy operating your application).

In my experience, it is advisable to keep large strings (and generally strings that could/should be altered at runtime by app operators) in configuration files.

Summary: use text blocks responsibly :).

The only way I know of is to concatenate multiple lines with plus signs

I got a bit annoyed with reading that multiline syntax is indeed been planned for jdk7 (after about how many decades of java existence?). Funnily, there is not even yet a readAll() function for reading the complete contents of a file (from jdk7 only, huhh), so the code below reads single lines.

/*
  MakeMultiline v1.0 (2010) - Free to use and copy.

  Small gadget to turn text blobs into one java string literal
  (doing the split in lines, adding \n at each end and enclosing
  in double quotes). Does escape quotes encountered in the text blob.

  Useful for working around missing multiline string syntax in java
  prior jdk7. Use with:

     java MakeMultiline "    "
  or
     java MakeMultiline "    " mytextfile.txt
*/

import java.io.*;

class MakeMultiline {
  public static void main(String[] args) {
    try {
      // args[0]: indent
      // args[1]: filename to read (optional; stdin if not given)
      // Beware the nmb of newlines at the end when using stdin!

      String indent = (args.length > 0 ? args[0] : "");
      FileReader fr = null; BufferedReader br;
      if (args.length > 1)
        { fr =  new FileReader(args[1]); br = new BufferedReader(fr); }
      else
        { br = new BufferedReader(new InputStreamReader(System.in)); }
      String s; String res = "";
      while((s = br.readLine()) != null) {
        if (res.length() > 0) res += " +\n";
        res += indent + "\"" + s.replace("\"", "\\\"") + "\\n\"";
      }
      br.close(); if (fr != null) fr.close();
      System.out.println(res + ";");
    }
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
  }
}

This was the quickest solution for me. (2010-01-27)

Saw all the answers and I think no one referred than in newer version of java you can do this:

String s = """
    This
    is 
    a
    multiline
    string
    """;
System.out.println(s);

This is what it prints:

This
is
a
multiline
string
Related