Java + String Concatenation: Choosing the Right Approach
java + string concatenation: Learn how Java handles string concatenation, compare +, concat(), StringBuilder, and StringBuffer, and choose the right approach for perfo...
java + string concatenation requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In Java, strings are immutable. Every time you concatenate two strings, a new String object is created, and the original objects remain unchanged. The way you combine strings affects both code readability and runtime performance. This article compares the main approaches: the + operator, the concat() method, StringBuilder, StringBuffer, and the Java 8+ joining utilities, and explains when each is the right choice.
How the + Operator Works in Java
The + operator is the most common way to concatenate strings in Java. For simple expressions like "Hello" + " " + "World", the compiler translates the expression into a StringBuilder chain internally. The bytecode typically creates a single StringBuilder, appends each part, and calls toString(). This optimization makes the + operator efficient for a fixed number of concatenations.
String greeting = "Hello" + " " + "World";
The compiler generates something equivalent to:
String greeting = new StringBuilder() .append("Hello") .append(" ") .append("World") .toString();
This works well when the number of parts is known at compile time. However, when concatenation happens inside a loop, the compiler cannot always reuse a single StringBuilder. 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, concat() does not accept null arguments; it throws a NullPointerException if the argument is null. It also does not accept any other type besides String, so you must convert non-string values explicitly.
String base = "Java"; String result = base.concat(" + String");
The concat() method is implemented by allocating a new character array and copying both strings into it. For a single concatenation, it is comparable to the + operator. However, it does not benefit from the compiler's StringBuilder optimization, and 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, while StringBuffer is synchronized and therefore thread-safe at the cost of performance.
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.
StringBuffer has the same API but synchronizes each method call. In single-threaded code, StringBuffer adds unnecessary overhead. Use StringBuilder unless you need to share the builder across threads.
Performance and Memory Behavior
The key difference between these approaches is the number of objects created. The + operator and concat() produce a new String for every 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 O(n) intermediate String objects, leading to high memory churn and garbage collection pressure. StringBuilder avoids that by reusing the same buffer.
For a fixed number of concatenations, the compiler's optimization of + often makes it as fast as StringBuilder. The choice should be based on whether the number of parts is known and whether the concatenation is in a loop.
Choosing the Right Concatenation Method
The following table summarizes the tradeoffs:
| Method | Mutability | Thread-safe | Use case |
|---|---|---|---|
+ operator | Immutable | N/A | Simple, fixed concatenations |
concat() | Immutable | N/A | Rarely needed; use + instead |
StringBuilder | Mutable | No | Repeated appends in a loop or dynamic building |
StringBuffer | Mutable | Yes | Shared builder across threads |
String.join | Immutable | N/A | Joining 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 you are concatenating in a highly concurrent environment, StringBuffer provides thread safety, but consider whether you really need it. Often, you can build the string locally and then publish it, avoiding the need for synchronization.