Back to Blog
Java

Java String Concat: Choosing the Right Approach

java string concat: Compare Java string concatenation options: the + operator, concat(), StringBuilder, and String.join. Learn which approach fits each scenario.

String ConcatenationStringBuilderJava PerformanceString APIJava Development
A diagram showing multiple Java string concatenation paths converging into a single efficient StringBuilder result.

Java offers several ways to perform java string concat: the + operator, the concat() method, StringBuilder, StringBuffer, String.join(), and String.format(). Each approach has different runtime behavior, memory characteristics, and readability tradeoffs. The choice affects not only code clarity but also how many intermediate objects the JVM creates during execution.

How the + Operator Behaves

The + operator is the most readable way to combine strings in everyday code:

String name = "Ada"; String greeting = "Hello, " + name + "!";

When the compiler sees string concatenation with +, it typically rewrites the expression using StringBuilder under the hood. For a single expression, this is efficient. The compiler generates bytecode that creates a StringBuilder, appends each part, and calls toString() at the end.

The behavior changes when concatenation happens inside a loop:

String result = ""; for (int i = 0; i < 1000; i++) { result = result + i + ","; }

Here, each iteration creates a new StringBuilder, appends the current result, appends the new value, and converts back to String. That means 1000 intermediate String objects are created, each holding a progressively larger copy of the accumulated data. The runtime cost is quadratic in the length of the accumulated string because each iteration copies the entire previous result.

The concat() Method

The String class provides concat() as an instance method:

String a = "Hello"; String b = a.concat(", world");

Unlike +, concat() accepts only a single String argument. It returns a new String that combines the receiver and the argument. If the argument is empty, concat() may return the original String object rather than creating a new one, depending on the JDK implementation.

The concat() method does not accept null. Passing null throws NullPointerException, whereas the + operator treats null as the literal text "null". This difference matters when concatenating values from external input.

StringBuilder for Repeated Concatenation

When building a string incrementally, StringBuilder is the standard choice:

StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i).append(','); } String result = sb.toString();

StringBuilder maintains a mutable character buffer. Each append() call writes directly into that buffer, growing it only when needed. No intermediate String objects are created until toString() is called at the end. This makes the runtime cost linear in the total output size rather than quadratic.

StringBuilder is not thread-safe. If multiple threads append to the same instance without external synchronization, the result is undefined. For concurrent scenarios, StringBuffer provides synchronized methods, but the synchronization overhead is rarely worth it. A better approach for concurrent string building is to have each thread build its own StringBuilder and combine the results afterward.

String.join and Other Collection-Based Approaches

When concatenating a collection of strings with a delimiter, String.join() is more direct than a manual loop:

List<String> names = List.of("Ada", "Grace", "Linus"); String csv = String.join(", ", names);

String.join() internally uses a StringJoiner, which wraps a StringBuilder. It handles delimiter placement automatically and avoids the trailing-delimiter bug that manual loops often introduce.

For cases where elements need transformation before joining, a stream pipeline works well:

String result = names.stream() .map(String::toUpperCase) .collect(Collectors.joining(", "));

The Collectors.joining() collector also uses StringBuilder internally and produces the same linear-time behavior.

Performance and Memory Tradeoffs

The dominant cost in string concatenation is object allocation and copying. Every String is immutable, so any concatenation that produces a new String must copy the characters of all inputs into a new backing array.

The + operator in a single expression is fine because the compiler generates efficient StringBuilder code. The problem appears when concatenation is spread across many statements or iterations, because each step allocates a new String and copies everything accumulated so far.

StringBuilder avoids that by keeping a single growable buffer. The buffer doubles in size when it fills, so the total copying work stays proportional to the final length. This is the mechanism behind the linear-time behavior, not a magic constant.

For very large strings, consider the initial capacity:

StringBuilder sb = new StringBuilder(1024);

Providing a reasonable initial capacity reduces the number of buffer resizes. The default capacity is ​16 characters, which forces frequent growth for large outputs. Estimating the final size is not always possible, but when it is, setting capacity avoids needless copying.

Choosing the the Right Approach

The decision depends on the shape of the concatenation:

ScenarioRecommended approach
Single expression with a few parts+ operator
Concatenating exactly two stringsconcat() or +
Building a string in a loopStringBuilder
Joining a collection with a delimiterString.join() or Collectors.jjoining()
Formatting with placeholdersString.format() or Formatter
Concurrent building across threadsSeparate builders, combine later

Use the + operator for readability when the expression is small and appears once. Use StringBuilder when the accumulation happens across multiple statements or iterations. Use String.join() when the input is already a collection and a delimiter is involved.

Avoid using + inside a loop to accumulate a result. The compiler cannot optimize across loop iterations, so each pass allocates a new String and copies the entire previous content. This is the most common performance mistake in java string concat code.

String.format() is convenient for templates, but it parses the format string and performs locale-sensitive formatting. For simple concatenation, it is slower than the the alternatives. Reserve it for cases where the format pattern genuinely adds value, such as padding, number formatting, or localization.

Compatibility Notes

The compiler optimization that rewrites + into StringBuilder has been present since Java 5. Code written for older JVMs may have relied on StringBuffer, but modern code should not. The behavior of + with null operands is defined: the the literal text "null" is inserted. The concat() method, by contrast,, throws NullPointerException for a null argument.

Java 15 introduced text blocks, which simplify multi-line string literals but do not change concatenation semantics. Java 21's string templates were previewed but removed in later revisions,, so production code should not depend on them.

The general rule across JDK versions: the the + operator in a single expression is optimized by the the compiler, while accumulation across iterations requires explicit StringBuilder use. This rule has been stable for many releases and is safe to rely on.

java string concat: Practical Usage and Code Examples | RYUSLOG DEV