How does the Java compiler translate the + string concatenation operator, and how has that changed in modern Java?
answer
- is syntactic sugar; no JVM concat instruction
- Java <=8: javac emits StringBuilder.append chain
- Java 9+ JEP 280: single invokedynamic
- bootstrap = StringConcatFactory.makeConcatWithConstants -> cached MethodHandle
- javap -c reveals which strategy
basics
~20 sThe + you write is not a real method on String. The compiler turns it into bytecode that actually builds the joined string. Older Java used a StringBuilder behind the scenes; modern Java uses a special hook called invokedynamic.
solid answer
~40 sString + is pure syntactic sugar; the JVM has no concatenation instruction. The javac compiler rewrites each concatenation expression into bytecode that does the real work. Through Java 8 it emitted explicit StringBuilder code: new StringBuilder().append(a).append(b).toString(). Since Java 9 (JEP 280) javac instead emits a single invokedynamic instruction that, on first execution, calls a bootstrap method in java.lang.invoke.StringConcatFactory. The factory builds and caches an optimized MethodHandle for that exact shape of concatenation, typically using a hidden, ahead-of-time-sized strategy that avoids intermediate StringBuilder churn. The benefit is that the strategy can evolve in the JDK without recompiling your code, and the JIT can inline a tighter, single-allocation result. You still write +; only the generated code changed.
go deeper
Knows + ultimately becomes some builder/byte code and not a literal method on String.
Names the pre-9 StringBuilder rewrite vs the Java 9+ invokedynamic/StringConcatFactory approach and why it changed.
Explains the bootstrap-once-then-cached MethodHandle, the single-allocation strategy, and the recompile-free upgrade benefit; can read javap output.
Discusses decoupling codegen from runtime strategy as a JDK design pattern and its implications for forward performance and startup cost trade-offs.
## The key idea: + is sugar, not an operation the JVM understands The Java Virtual Machine bytecode has no "concatenate strings" instruction. So when you write `a + b` with strings, the **compiler** (`javac`) must translate that into instructions that genuinely produce the joined string. The text `a + b` is just convenient *syntax* — "syntactic sugar" — for whatever code the compiler chooses to emit. ## The old strategy (Java 1.5 through 8): explicit StringBuilder `StringBuilder` is a *mutable* text buffer: unlike `String`, you can keep appending to it cheaply, then call `toString()` once to get a finished `String`. For years `javac` rewrote `a + b + c` into roughly: ``` new StringBuilder().append(a).append(b).append(c).toString() ``` This was reasonable but rigid: the exact bytecode (and any inefficiency, like not pre-sizing the buffer) was **baked into your compiled .class file**. Improving it required recompiling. ## The modern strategy (Java 9+, JEP 280): invokedynamic + StringConcatFactory `invokedynamic` ("indy") is a bytecode instruction that means "call a target I will figure out the first time I run." The first time the line executes, the JVM calls a **bootstrap method** — here `java.lang.invoke.StringConcatFactory.makeConcatWithConstants(...)`. That factory examines the *shape* of this particular concatenation (how many parts, their types, any constant pieces) and returns a `MethodHandle` — a fast, directly-callable function — that performs the join. The handle is **linked once and cached**, so later executions jump straight to it with no re-bootstrapping. The factory is free to pick the best implementation: a common one computes the total length up front and fills a single backing array **once**, avoiding the resize-and-copy churn a naive `StringBuilder` chain can cause. ## Why move the logic out of your .class file The payoff is **decoupling**: the *strategy* lives in the JDK runtime, not in your compiled code. A newer JDK can ship a smarter concatenation implementation and your **already-compiled** program automatically benefits — no recompile. The JIT compiler can also inline the handle into a tight, often single-allocation routine. ## What did NOT change You still write `+`. Semantics are identical (immutability, left-to-right, null→"null"). Only the **generated bytecode** differs. You can confirm this with `javap -c YourClass` (or `javap -v`): pre-9 you see `StringBuilder` calls; 9+ you see an `invokedynamic ... makeConcatWithConstants` line. ## Practical note This machinery optimizes a *single* concatenation expression. It does **not** rescue a `+` inside a loop where each iteration is a separate expression — that still creates a fresh string each pass (see the loop-pitfall question).
- What is the practical benefit of moving the concat logic to StringConcatFactory instead of emitting StringBuilder in the .class?Decoupling: the JDK can improve the concatenation strategy (e.g. single pre-sized allocation) and already-compiled programs benefit on a newer runtime without recompilation, and the JIT can inline a tighter result.
- How can you see which strategy a class was compiled with?Disassemble with javap -c (or -v): older code shows StringBuilder append/toString calls; Java 9+ shows an invokedynamic instruction bound to StringConcatFactory.makeConcatWithConstants.
saying these in an interview costs you the question
- Saying String + is a method call on String
- Claiming invokedynamic re-bootstraps on every execution (it links once)
- Thinking JEP 280 changed the language semantics rather than codegen
- Believing the indy strategy also fixes + in loops