Skip to main content

String Concatenation

Post JDK-9: + Outside of Loops

Although the often suggested StringBuilder is more performant in loops From JDK-9 and onwards, it is preferable in terms of readability and close to equal performance to use the + operator for short string concatenations.

Although String.format() is generally more readable than it's counterparts of String concatenation and StringBuilder , the performance of String.format() is far worse, and for a smaller number of operations the difference in performance between StringBuilder and the + operation is negligible.

String formatValues(String firstName, String lastName, int age) {
return String.format("Welcome %s %s, you are %d years old!", firstName, lastName, age);
}

String concatenateValues() {
return "Welcome " + firstName + " " + lastName + ", you are " + age + " years old!";
}

String stringBuilderValues() {
return StringBuilder("Welcome ").append(firstName).append(" ").append(lastName)
.append(", you are ").append(age).append(" years old!").toString();
}

Benchmark

Definition

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 200, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class StringConcatenationBenchmark {

private String uuid1;
private String uuid2;
private String uuid3;

@Setup
public void setup() {
uuid1 = UUID.randomUUID().toString();
uuid2 = UUID.randomUUID().toString();
uuid3 = UUID.randomUUID().toString();
}

@Benchmark
public String concatenate_string() {
return uuid1 + " - " + uuid2 + " - " + uuid3;
}

@Benchmark
public String format_string() {
return String.format("%s - %s - %s", uuid1, uuid2, uuid3);
}

@Benchmark
public String string_builder() {
return new StringBuilder(uuid1).append(" - ").append(uuid2).append(" - ").append(uuid3).toString();
}
}

Results

As can be seen from the results below, there's a marginal performance increase of 2.4% using StringBuilder over concatenation. However, there's a 667.7% increase in performance using + compared to String.format()!

BenchmarkModeCountScoreErrorUnits
StringConcatenationBenchmark.concatenate_stringavgt200121.715± 16.742ns/op
StringConcatenationBenchmark.format_stringavgt200946.532± 271.592ns/op
StringConcatenationBenchmark.string_builderavgt200124.590± 41.864ns/op

Pre JDK-9: StringBuilder over Concatenation

When performing String concatenation in Java, it is very easy to fall into the trap of utilising the + operator to combing multiple strings together. However, this approach has major performance ramifications if performed a large number of times. Default String concatenation requires both strings contents to be copied, resulting in quadratic runtime (O(n2)).

Strings in Java are immutable - meaning that operations on a String never change the value of the String. Each time String A is concatenated with String B, memory is allocated to allow space for the combined length of the 2 new strings; then the original reference to String A is replaced with its new concatenated form.

StringBuilder in contrast uses an internal array to copy over the characters, increasing the size of the array if needed when adding new Strings.

public class TestStringConcatenation {
String usingStringConcatenation(String stringToAppend) {
String startingString = "";

for (int i = 0; i < 10_000; i++) {
startingString += stringToAppend;
}
return startingString;
}

String usingStringBuilder(String stringToAppend) {
StringBuilder sb = new StringBuilder();

for (int i = 0; i < 10_000; i++) {
sb.append(stringToAppend);
}
return sb.toString();
}
}

This can result in massive performance improvements when concatenations are run numerous times. To improve performance further - if you know the size of the final string ahead of time, pre-allocate the StringBuilder's size.

StringBuilder sb = new StringBuilder(INITIAL_SIZE);

Multi-threaded Environments

Minor caveat - if performing concatenation in a multi-threaded environment - then StringBuffer should be used as it provides synchronisation for threads, although less performant than StringBuilder.