skip to content

How does the Java compiler translate the + string concatenation operator, and how has that changed in modern Java?

level: middleimportance: must knowfreq 68%

answer

  1. is syntactic sugar; no JVM concat instruction
  2. Java <=8: javac emits StringBuilder.append chain
  3. Java 9+ JEP 280: single invokedynamic
  4. bootstrap = StringConcatFactory.makeConcatWithConstants -> cached MethodHandle
  5. javap -c reveals which strategy

basics

~20 s

The + 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 s

String + 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

for a junior

Knows + ultimately becomes some builder/byte code and not a literal method on String.

for a middle

Names the pre-9 StringBuilder rewrite vs the Java 9+ invokedynamic/StringConcatFactory approach and why it changed.

for a senior

Explains the bootstrap-once-then-cached MethodHandle, the single-allocation strategy, and the recompile-free upgrade benefit; can read javap output.

for a principal

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

context