## As of [[OpenJDK|JDK]]9, `StringBuilder` is no longer the fastest option for simple string concatenation. The compiler uses [[InvokeDynamic]] and `StringConcatFactory` to optimize string concatenation operations without incurring extra copy operations. - [[OpenJDK]] [JEP 280](https://openjdk.java.net/jeps/280) - [[Java Compiler Optimizations for String Concatenation]] - [Concatenating Strings In Java 9](https://dzone.com/articles/concatenating-strings-in-java-9) > What this generally means is that concatenating String values with the + operator turns out to be faster when compared to using StringBuilder, which was earlier used in the Java 8 bytecode when simple + concatenation optimizations were applied. - [JDK 9/JEP 280: String Concatenations Will Never Be the Same](https://dzone.com/articles/jdk-9jep-280-string-concatenations-will-never-be-t?fromrel=true) > There's an interesting implication of this in terms of using [StringBuffer](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StringBuffer.html) (which [I have a difficult time](http://marxsoftware.blogspot.com/2017/04/stringbuffer-implications.html) finding a [good use for anyway](https://vanilla-java.github.io/2017/04/13/String-Buffer-and-how-hard-it-is-to-get-rid-of-legacy-code.html)) and [StringBuilder](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/StringBuilder.html). It was a stated "Non-Goal" of JEP 280 to not "introduce any new `String` and/or `StringBuilder` APIs that might help to build better translation strategies." Related to this, for simple string concatenations like that shown in the code example at the beginning of this post, explicit use of `StringBuilder` and `StringBuffer` will actually preclude the ability for the compiler to make use of the [JEP 280](https://openjdk.java.net/jeps/280)\-introduced feature discussed in this post. ### Considerations #### Tuning [StringConcatFactory](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java) offers different [strategies](https://github.com/openjdk/jdk/blob/687ce3e7bb6d45ef67037dd77acd58aa48bbd724/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L136) to generate the `CallSite` divided in byte-code generator using ASM and MethodHandle-based one. - [BC_SB](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L137): [generate the byte-code](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L795) equivalent to what `javac` generates in Java 8. - [BC_SB_SIZED](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L143): [generate the byte-code](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L795) equivalent to what `javac` but try to estimate the initial size of the `StringBuilder`. - [BC_SB_SIZED_EXACT](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L149): [generate the byte-code](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L795) equivalent to what `javac` but compute the exact size of the `StringBuilder`. - [MH_SB_SIZED](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L155): [combines MethodHandles](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L1232) that ends up calling the `StringBuilder` with an estimated initial size. - [MH_SB_SIZED_EXACT](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L161): [combines MethodHandles](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L1232) that ends up calling the `StringBuilder` with an exact size. - [MH_INLINE_SIZED_EXACT](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L167): [combines MethodHandles](https://github.com/dmlloyd/openjdk/blob/jdk9/jdk9/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java#L1467) that creates directly the String with an exact size byte\[\] with no copy. The default and most [performant](http://cr.openjdk.java.net/~shade/8085796/notes.txt) one is `MH_INLINE_SIZED_EXACT` that can lead to 3 to 4 times performance improvement. You can override the `Strategy` on the command line by defining the property `java.lang.invoke.stringConcat`. #### loops While avoiding needless copying **You will still incur** intermediate object creation costs if the string construction is in a loop. The compiler isn't smart enough yet to infer a single `StringBuilder` scoped outside the loop could be used instead of creating intermediate objects. [stackoverflow post with microbenchmarks](https://stackoverflow.com/questions/49458564/string-concatenation-in-a-for-loop-java-9) ```java @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) @State(Scope.Thread) public class LoopTest { public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder().include(LoopTest.class.getSimpleName()) .jvmArgs("-ea", "-Xms10000m", "-Xmx10000m") .shouldFailOnError(true) .build(); new Runner(opt).run(); } @Param(value = {"1000", "10000", "100000"}) int howmany; @Fork(1) @Benchmark public String concatBuilder(){ StringBuilder sb = new StringBuilder(); for(int i=0;i<howmany;++i){ sb.append(i); } return sb.toString(); } @Fork(1) @Benchmark public String concatPlain(){ String result = ""; for(int i=0;i<howmany;++i){ result +=i; } return result; } } ``` ``` LoopTest.concatPlain 100000 avgt 5 3902.711 ± 67.215 ms/op LoopTest.concatBuilder 100000 avgt 5 1.850 ± 0.574 ms/op ```