Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

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.

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.37</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.37</version>
</dependency>

3. Immutable String Concatenation

Immutable string concatenation involves creating a new immutable String instance for each concatenation operation. Every time a concatenation occurs, a new string object is generated. This method is simple and straightforward but can be less memory efficient due to the creation of multiple objects.

Now, let’s take a quick look at various immutable methods:

3.1. Using Addition (+) Operator

This is the simplest way and probably the one we’re most familiar with. It can concatenate string literals, variables, or a combination of both using addition + operator:

String str1 = "String";
String str2 = "Concat";
String result = str1 + str2;

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 100 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.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!