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.