Question 7 Β· Section 12

What Happens When Concatenating Strings with + Operator?

When you use + to join strings, Java creates a new string from two (or more) source strings. Since String is immutable, the original strings don't change.

Language versions: English Russian Ukrainian

🟒 Junior Level

When you use + to join strings, Java creates a new string from two (or more) source strings. Since String is immutable, the original strings don’t change.

Example:

String s1 = "Hello";
String s2 = "World";
String result = s1 + " " + s2; // "Hello World"

Important point: If you concatenate strings in a loop via +, a new string is created on each iteration. This is very inefficient.

// BAD β€” creates N new String objects
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i; // new String on every iteration!
}

// GOOD β€” one StringBuilder object
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}

Rule: + is convenient for simple expressions (1-2 strings), but forbidden in loops.

When + hurts readability

Complex expressions with nested method calls: "User: " + user.getName() + " (" + user.getAge() + " years)" β€” better with StringBuilder or formatting.


🟑 Middle Level

How it works

Behavior depends on whether values are known at compile time:

Constant concatenation (Compile-time Constant Folding):

String s = "Hello" + " " + "World";
// Compiler turns this into:
String s = "Hello World"; // One string in bytecode, 0 operations at runtime

Variable concatenation (Runtime):

String a = getName();
String b = getLastName();
String full = a + " " + b;

What happens β€œunder the hood” depends on the Java version (see Senior section).

Typical mistakes

  1. Mistake: + in a loop Solution: StringBuilder outside the loop

  2. Mistake: split(".") β€” dot is a regex metacharacter Solution: split("\\.") or split(Pattern.quote("."))

  3. Mistake: null + string Solution: Result will be "nullWorld". This is not an error, but can be surprising.

Approach comparison

| Scenario | Approach | Efficiency | | β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”- | β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”- | β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” | | "A" + "B" + "C" | + | Great (constant folding) | | a + b + c (variables, 1 line) | + | Great (optimized) | | Loop with += | StringBuilder | + β€” O(nΒ²), StringBuilder β€” O(n) | | Formatting | String.format() or StringBuilder| Depends on context |


πŸ”΄ Senior Level

Internal Implementation β€” Evolution

Java 5-8: StringBuilder codegen The compiler replaced a + b + c with:

new StringBuilder().append(a).append(b).append(c).toString()

Problem: bytecode is β€œhardcoded” in .class. A new optimization strategy would require recompilation.

JEP 280 (Java 9) introduced invokedynamic for concatenation. By default, Java 9 uses BC_SB, and in Java 11+ the strategy can be changed via StringConcatFactory.

String full = a + " " + b;

Bytecode:

0: aload_1  // a
1: aload_2  // " "
2: aload_3  // b
3: invokedynamic #7  // makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
8: astore 4

invokedynamic references StringConcatFactory. On first call, the bootstrap method generates optimal code for this specific concatenation.

StringConcatFactory strategies:

  1. MH_LF (MethodHandle with LambdaForm): Generates a MethodHandle for concatenation
  2. BC_SB (Bytecode StringBuilder): Generates bytecode with StringBuilder
  3. BH_SB (Bootstrap method with StringBuilder β€” compact): Optimized variant

Architectural Trade-offs

invokedynamic pros:

  • JVM chooses strategy at runtime (adaptivity)
  • New strategies can be added without recompilation
  • Fewer allocations β€” JVM knows all arguments and allocates byte[] of the right size immediately

Cons:

  • Bootstrap overhead on first call (~1-5ΞΌs)
  • Harder to debug bytecode

Edge Cases

  1. null concatenation: "val: " + null β†’ "val: null". StringConcatFactory and StringBuilder call String.valueOf(null) β†’ "null".

  2. Mixed types: "Value: " + 42 β†’ StringConcatFactory knows the int type and uses optimal conversion without creating an intermediate String.

  3. Loops: invokedynamic optimizes only one expression. A loop still creates a new concatenation context on each iteration:

    for (int i = 0; i < n; i++) s += i;
    // Each iteration: invokedynamic β†’ new byte[] β†’ new String
    // Total: O(nΒ²) allocations
    

Performance

| Scenario | Java 8 (StringBuilder) | Java 9+ (invokedynamic) | | β€”β€”β€”β€”β€”- | β€”β€”β€”β€”β€”β€”β€”- | ———————– | | 3 variables | ~30ns | ~15ns | | 10 variables | ~80ns | ~30ns | | 3 variables + int| ~35ns | ~18ns | | Loop 10K iters | O(nΒ²) | O(nΒ²) (not optimized!) |

Production Experience

Scenario: Logging log.info("User " + user.getId() + " action " + action) β€” 100K calls/sec:

  • Java 8: 100K new StringBuilder() β†’ ~30ms CPU
  • Java 9+: invokedynamic with direct allocation β†’ ~15ms CPU
  • If log is filtered (level < INFO): both variants waste CPU. Solution: if (log.isInfoEnabled())

Best Practices for Highload

  • For simple concatenations (1 line of code): + β€” readable and efficient
  • In loops: always StringBuilder with initialCapacity
  • For formatting: String.format() is convenient but slower (pattern parsing). Alternatives: MessageFormat, text blocks (Java 15+), or manual StringBuilder
  • Lazy evaluation: if concatenation is expensive and may not be needed β€” defer it

🎯 Interview Cheat Sheet

Must know:

  • + operator creates a NEW string β€” String is immutable, originals don’t change
  • Constant folding: "A" + "B" + "C" β†’ "ABC" at compile time, 0 operations at runtime
  • Java 5-8: compiler replaces a + b + c with new StringBuilder().append(a).append(b).append(c).toString()
  • Java 9+: invokedynamic + StringConcatFactory β€” JVM chooses optimal strategy at runtime
  • In loops + creates O(nΒ²) allocations β€” use StringBuilder
  • null + "text" β†’ "nulltext" β€” not an error, but can be surprising

Frequent follow-up questions:

  • Why is + in a loop bad? β€” Each iteration creates a new StringBuilder + new String. For 1000 iterations: 1000 StringBuilder + 1000 String = O(nΒ²).
  • What is invokedynamic for concatenation? β€” In Java 9+ the compiler generates invokedynamic makeConcatWithConstants. On first call, JVM chooses optimal strategy (MH_LF, BC_SB, BH_SB).
  • Does the compiler optimize loops with +? β€” No. invokedynamic optimizes only a single expression, not a loop.
  • What is StringConcatFactory? β€” A factory in java.lang.invoke that generates optimal code for a specific concatenation.

Red flags (DON’T say):

  • ❌ β€œ+ in a loop β€” is fine” β€” O(nΒ²) allocations, use StringBuilder
  • ❌ β€œCompiler optimizes + in loops” β€” optimizes only a single expression, not a loop
  • ❌ β€œinvokedynamic is slower than StringBuilder” β€” for a single expression β€” faster, for a loop β€” equally bad
  • ❌ β€œConcatenation via + modifies original strings” β€” String is immutable, always creates a new one

Related topics:

  • [[8. How Java Compiler Optimizes String Concatenation]]
  • [[5. When to Use StringBuilder vs StringBuffer]]
  • [[4. Why String is Immutable]]