Java strings are immutable. Concatenation with +
is compiled to use a builder under the hood, but the lifecycle of that builder
depends on where the expression sits. In tight loops, using +
can create many short‑lived objects; a single StringBuilder
reused across iterations avoids those allocations.
-
What gets created with +
For a single expression like "Hello " + name + "!",
the compiler emits something like new StringBuilder().append(...).toString().
That means 1 StringBuilder instance and 1 resulting
String object at runtime.
// Roughly equivalent desugaring
String result = new StringBuilder()
.append("Hello ")
.append(name)
.append("!")
.toString();
Inside a loop, however, s += part; becomes
new StringBuilder(s).append(part).toString() on every iteration:
that is 1 new StringBuilder and 1 new
String per iteration.
String s = "";
for (String part : parts) {
s += part; // each iteration: new StringBuilder + new String
}
-
What gets created with StringBuilder
With an explicit StringBuilder, you allocate 1 builder and 1 final
String. Intermediate steps are in the same mutable buffer.
StringBuilder sb = new StringBuilder();
for (String part : parts) {
sb.append(part);
}
String s = sb.toString(); // one final String object
-
Compile‑time constants are folded
Concatenations of literals are resolved at compile time with zero runtime allocation.
String a = "Hello" + ", " + "world"; // constant folded by compiler
-
Java 9+ note: invokedynamic concat
Modern JDKs may use StringConcatFactory via invokedynamic
for +. The guidance remains: prefer a single builder in loops.
-
Practical guidelines
- Use
+ for a handful of concatenations outside loops.
- In loops, use
StringBuilder.
- Pre‑size the builder when possible:
new StringBuilder(estimatedSize).
- Prefer
String.join for joining with delimiters.