Back to Blog
Java

Java + String Concatenation: Choosing the Right Approach

Learn how Java handles string concatenation, compare +, concat(), StringBuilder, and StringBuffer, and choose the right approach for performance and readability.

StringBuilderStringBufferJava StringsString ConcatenationJava Performance
Illustration of Java string concatenation showing different methods like +, StringBuilder, and StringBuffer.

String concatenation is a common operation in Java, but the approach you choose affects code readability and runtime performance. This article compares the + operator, concat(), StringBuilder, StringBuffer, and the Java 8+ joining utilities, and explains when each is the right choice.

Strings are immutable in Java: when you concatenate at runtime, a new String object is created and the original objects remain unchanged. The following sections describe the syntax, behavior, and trade-offs of each approach.

How the + Operator Works in Java

The + operator is the most common way to concatenate strings in Java. For a runtime expression such as a + " + " + b, where a and b are non-final String variables, the compiler treats the whole expression as one concatenation operation. In many JDK versions this generated a single StringBuilder chain; the exact instruction sequence can vary, but the effect is the same: the concatenation is done in a single pass rather than as repeated String copies. This optimization makes + efficient for a fixed concatenation expression.

String a = "Java"; String b = "String"; String result = a + " + " + b;

The effect is equivalent to:

String result = new StringBuilder() .append(a) .append(" + ") .append(b) .toString();

This works well when the concatenation is written as a single expression with a fixed number of parts. However, when concatenation happens inside a loop, the compiler cannot combine the iterations into a single builder. Each iteration may create a new StringBuilder and a new intermediate String, leading to unnecessary allocation and copying.

String result = ""; for (int i = 0; i < 100; i++) { result += i; // Each iteration creates a new StringBuilder and String }

In this loop, every += creates a new StringBuilder, appends the current result, appends the number, and then converts to a new String. This is O(n²) in the number of iterations and should be avoided.

The concat() Method and Its Limits

The concat() method is a member of the String class. It concatenates the specified string to the end of the current string and returns a new String. Unlike the + operator, the argument must be a String; for other types you must convert explicitly. The argument cannot be null; calling concat(null) throws a NullPointerException.

String base = "Java"; String result = base.concat(" + String");

The concat() method does not benefit from the compiler's optimization used for +, so chaining multiple concat() calls creates intermediate strings.

String result = "a".concat("b").concat("c"); // Creates two intermediate strings

In practice, concat() is rarely used because the + operator is more readable and equally efficient for simple cases.

StringBuilder and StringBuffer: Mutable Builders

StringBuilder and StringBuffer are mutable sequences of characters. They allow you to build a string incrementally without creating a new object at each step. StringBuilder is not thread-safe; StringBuffer synchronizes each method call, so its individual operations are thread-safe. In single-threaded code, StringBuffer adds unnecessary overhead.

StringBuilder sb = new StringBuilder(); sb.append("Java"); sb.append(" + "); sb.append("String"); String result = sb.toString();

The internal buffer grows as needed. When the buffer is full, it expands, typically doubling in size, and copies the existing content. This amortized growth makes repeated appends efficient.

Use StringBuilder unless you have a concrete need to share the builder across threads.

Performance and Memory Behavior

The key difference between these approaches is the number of objects created. In runtime code, the + operator and concat() produce a new String for every evaluated concatenation operation. StringBuilder and StringBuffer accumulate characters in a mutable buffer and produce a single String at the end.

In a loop that concatenates many parts, using + creates an intermediate String for each iteration, leading to high memory churn and garbage collection pressure. The repeated copying makes the total work roughly quadratic in the number of iterations. StringBuilder avoids that by reusing the same buffer.

For a fixed concatenation expression, the compiler's optimization of + often makes it as fast as StringBuilder. The choice should be based on whether the concatenation is a single fixed expression or part of a repeated accumulation loop.

Choosing the Right Concatenation Method

The following table summarizes the tradeoffs:

MethodMutabilityThread-safeUse case
+ operatorImmutableN/ASimple, fixed concatenations
concat()ImmutableN/ARarely needed; use + instead
StringBuilderMutableNoRepeated appends in a loop or dynamic building
StringBufferMutableYesShared builder across threads
String.joinImmutableN/AJoining a collection with a delimiter

Use StringBuilder when you are building a string dynamically, such as in a loop or when assembling a large output. Use StringBuffer only when the builder is accessed by multiple threads. For simple concatenations, the + operator is idiomatic and efficient.

Java 8+ Alternatives: String.join and Collectors.joining

Java 8 introduced String.join() and Collectors.joining() for joining sequences of strings with a delimiter. These are not general-purpose concatenation tools but are useful when you have a String[], Iterable, or Stream<String>.

String[] parts = {"Java", "String", "Concatenation"}; String result = String.join(" + ", parts);
List<String> words = Arrays.asList("Java", "String", "Concatenation"); String result = words.stream().collect(Collectors.joining(" + "));

These methods use StringJoiner internally, which is similar to StringBuilder but optimized for joining with delimiters. They are the preferred choice when you need to combine a collection of strings with a separator.

Common Pitfalls and Edge Cases

One common mistake is using + inside a loop without realizing the performance cost. Another is assuming concat() handles null gracefully. Always check for null before calling concat(), or use String.valueOf() to convert null to the literal "null".

When using StringBuilder, remember that append() returns the builder itself, allowing method chaining. This can improve readability but does not change performance.

Also note that StringBuilder and StringBuffer are not String subclasses. They cannot be used where a String is expected without calling toString(). This is a common source of compilation errors.

Finally, if multiple threads need to call methods on the same builder, StringBuffer gives you synchronized methods, but consider whether you really need it. Often, you can build the string locally and then publish it, avoiding the need for synchronization.

Java + String Concatenation: Methods, Performance, and Code Examples | RYUSLOG DEV