1. Overview

In Java, string concatenation is a common operation when working with text manipulation. However, the way you choose to concatenate strings can have a significant impact on your application’s performance. It’s crucial to understand the different concatenation methods available and their performance characteristics to write efficient and optimized code.

In this tutorial, we’ll dive into different string concatenation methods in Java. We’ll benchmark and compare the execution times of these methods using tools JHM.

2. Benchmarking

We’ll adopt JMH (Java Microbenchmark Harness) for our benchmarking. JMH provides a framework for measuring the performance of small code snippets, enabling developers to analyze and compare different implementations.

Before we proceed, let’s set up our environment to run the benchmarks. Both Core and Annotation Processors can be found in Maven Central.

3.2. Using concat() Method

The concat() method is provided by the String class and can be used to concatenate two strings together:

String str1 = "String";
String str2 = "Concat";
String result = str1.concat(str2);

3.3. Using String.join() Method

The String.join() is a new static method from Java 8 onward. It allows concatenating multiple strings using a specified delimiter:

String str1 = "String"; 
String str2 = "Concat"; 
String result = String.join("", str1, str2);

3.4. Using String.format() Method

String.format() is used for formatting strings with placeholders and format specifiers. It allows you to create formatted strings by replacing placeholders with actual values:

String str1 = "String"; 
String str2 = "Concat"; 
String result = String.format("%s%s", str1, str2);

3.5. Using Java Stream API

Finally, we’ll take a look at Java Stream API, which is also available from Java 8. It provides an expressive way to perform operations on collections of object and allow us to concentrate strings using Collectors.joining():

List<String> strList = List.of("String", "Concat");
String result = strList.stream().collect(Collectors.joining());

4. Mutable String Concatenation

Now let’s shift our focus to the mutable category. This refers to the process of concatenating strings using a mutable character sequence, where the underlying object can be modified to append or insert characters. Mutable concatenation is efficient and doesn’t require creating new objects for each operation.

Let’s have a look at the available mutable methods:

4.1. Using StringBuffer

StringBuffer provides a mutable sequence of characters. It allows for dynamic manipulation of strings without creating new objects. It’s worth mentioning that it’s designed to be thread-safe, meaning it can be safely accessed and modified by multiple threads concurrently:

StringBuffer buffer = new StringBuffer();
buffer.add("String"); 
buffer.add('Concat"); 
String result = buffer.toString();

4.2. Using StringBuilder

StringBuilder serves the same purpose as StringBuffer. The only difference between them is StringBuilder isn’t thread-safe, while StringBuffer is. It’s perfect for single-threaded scenarios where thread safety is not a concern:

StringBuilder builder = new StringBuilder(); 
builder.add("String"); 
builder.add('Concat"); 
String result = builder.toString();

4.3. Using StringJoiner

StringJoiner is a new class from Java 8 onward. Its function is similar to StringBuilder, providing a way to join multiple strings with a delimiter. While it is the same as StringBuilder, StringJoiner isn’t thread-safe:

StringJoiner joiner = new StringJoiner("");
joiner.add("String");
joiner.add('Concat");
String result = joiner.toString();

5. Performance Evaluation

In this section, we’ll evaluate the performance of the different string concatenation methods in various scenarios, including loop iterations and batch processing.

5.1. Loop Iteration

We’ll assess string concatenation performance within a loop, where strings are repeatedly concatenated. In this scenario, we’ll evaluate the performance of different methods with different numbers of iterations.

We’ll run tests with different iterations (100, 1000, and 10000) to see how computation time scales with the number of iterations. Let’s start with immutable methods:

Number of Iterations

Methods

100

1000

10000

+ Operator

3.369

322.492

31274.622

concat()

3.479

332.964

32526.987

String.join()

4.809

331.807

31090.466

String.format()

19.831

1368.867

121656.634

Stream API

10.253

379.570

30803.985

Now, we can see the performance of mutable methods:

Number of Iterations

Methods

100

1000

10000

StringBuffer

1.326

13.080

128.746

StringBuilder

0.512

4.599

43.306

StringJoiner

0.569

5.873

59.713

From the figures above, we can observe distinct behavior between these categories regarding computation time increase with the number of iterations.

The computation time increases linearly with the data size in the mutable category. Whereas in the immutable category, the computation time increases exponentially. A ten-fold increase in concatenation operations results in a hundred-fold increase in computation time.

We can also observe that most methods in the mutable category exhibit similar computation times, except for String.format(). It is notably slower that taking a few times longer than other methods in the same category. *The significant performance difference can be attributed to the additional parsing and replacement operations performed by String.format().*

Among the immutable category, StringBuilder is the fastest option due to its lack of synchronization overhead compared to StringBuffer. On the other hand, StringJoiner exhibits a slightly slower performance than StringBuilderbecause it needs to append the delimiter each time it concatenates.

5.2. Batch Processing

Let’s go through some methods that allow concatenating more than 2 strings in one go in the mutable category. The example below illustrates a single String.join() with 5 concatenations:

String result = String.join("", str1, str2, str3, str4, str5);

We’ll access the performance of these methods in this section. Similar to the previous section, we’ll run tests with different numbers of concatenations (100, 1000, and 10000) in a batch:

Number of Concatenation

Methods

100

1000

10000

+ Operator

0.777

33.768

StackOverflowError

String.join()

0.820

8.410

88.888

String.format()

3.871

38.659

381.652

Stream API

2.019

18.537

193.709

When we concatenate strings in batch, we observed linear computation time growth. Again, String.format() comes last again which is expected due to the additional parsing overhead.

6. Conclusion

In this article, we’ve explored different string concatenation methods in Java and evaluated their performance using JMH.

We observed distinct behaviour between mutable and immutable categories through performance evaluation in loop iteration. Computation time in the mutable category increased linearly with data size, while the immutable category showed exponential growth. Due to the scale pattern, we should adopt mutable methods whenever we concatenate strings within a loop.

Among all methods that we presented overall, StringBuilder stands out as the fastest one, whereas String.format() is the slowest one due to its additional parsing and replacement operations.

As usual, all the source code is available over on GitHub.