Which string expressions get pooled at compile time, and why does "a"+"b" == "ab" but s+"b" (with s a variable) does not?
answer
- Constant folding pools all-literal/final-constant concatenations
- Variable operand -> runtime concat -> fresh, un-pooled
- final + constant initializer = compile-time constant
- Runtime concat uses StringConcatFactory (Java 9+)
- intern() to pool a runtime result
basics
~20 sThe compiler folds expressions made only of constants (literals and final constants) into a single literal, which gets pooled. So "a"+"b" becomes "ab" at compile time and equals the pooled "ab". But if a variable is involved, the join happens at runtime and produces a fresh, un-pooled object.
solid answer
~50 sJava performs constant folding: an expression composed entirely of compile-time constants — string literals, and final variables initialized with constants — is evaluated by the compiler into a single constant string, which is interned in the pool. So "a"+"b" is compiled to the literal "ab" and is the same pooled object as "ab", making == true. When any operand is a non-constant variable (or a non-final variable, or new String), the concatenation is deferred to runtime; the JVM builds a fresh String (historically via StringBuilder, now via invokedynamic/StringConcatFactory) that is NOT in the pool, so == against the pooled literal is false. A final variable holding a constant counts as a compile-time constant and participates in folding; a non-final variable does not. To pool a runtime result you must call intern(). This is why == comparisons on strings are fragile and equals() is the safe default.
go deeper
Recognizes that "a"+"b" equals "ab" by value and should be compared with equals().
Knows literal-only concatenations are pooled while variable concatenations are not, and can call intern() to pool a result.
Explains compile-time constant folding, the final-with-constant-initializer rule, and the runtime concat machinery (StringConcatFactory).
Articulates why == on strings is an anti-pattern, the maintenance fragility of folding-dependent code, and sets lint/review rules to forbid relying on string identity.
## The mechanism: constant folding The Java compiler (`javac`) evaluates **compile-time constant expressions** at compile time and replaces them with their result. For strings, a constant expression made of literals and constant operands collapses into a single string **literal** that is placed in the class file's constant pool and therefore **interned**. A **compile-time constant** (per the JLS) is: - a string literal (`"a"`), - a `final` variable initialized with a constant expression (`final String X = "a";`), - and combinations of these with `+`. ## Case A: all constants -> pooled ```java String ab = "ab"; String joined = "a" + "b"; // folded by javac to the literal "ab" ab == joined; // true — same pooled object ``` Because `"a" + "b"` is fully constant, `javac` computes `"ab"` and emits it as a single literal; both names reference the one pooled instance. ## Case B: a variable operand -> runtime, not pooled ```java String s = "a"; String joined = s + "b"; // s is a (non-final) variable -> runtime concat "ab" == joined; // false — joined is a fresh heap object ``` Here `s` is **not** a compile-time constant, so the concatenation cannot be folded. The compiler emits code that builds the string at runtime. Historically this used a `StringBuilder`; since Java 9 it uses an `invokedynamic` call to `StringConcatFactory`. Either way the produced `String` is **brand new** and **not** in the pool, so `==` against the pooled `"ab"` is `false` (though `.equals()` is `true`). ## Case C: final variable -> counts as constant ```java final String s = "a"; // final + constant initializer => compile-time constant String joined = s + "b"; // folded to "ab" "ab" == joined; // true ``` Making `s` `final` with a constant initializer turns it back into a compile-time constant, so folding applies again and the result is pooled. ## Case D: non-final or computed -> not constant ```java String s = getValue(); // method call -> not constant, even if final final String s2 = getValue();// final but initializer not constant -> not compile-time constant ``` A `final` whose initializer is itself not a constant (e.g. a method result) is **not** a compile-time constant, so no folding. ## Forcing pooling at runtime When folding does not apply, use `intern()`: ```java String s = "a"; ("ab".equals((s + "b").intern())); // intern() returns the pooled "ab" "ab" == (s + "b").intern(); // true ``` ## Why it matters 1. **`==` on strings is fragile.** Whether two equal-valued strings are `==` depends on subtle folding rules that are easy to break (change `final`, introduce a variable, refactor a concat). Use `.equals()`. 2. **Performance/memory understanding.** Knowing that runtime concatenation allocates explains hidden object churn in loops. 3. **Interview signal.** Predicting these `==` results correctly demonstrates real understanding of the pool, folding, and immutability. ## Summary | Expression | Folded? | `== "ab"` | |---|---|---| | `"a" + "b"` | yes | true | | `s + "b"` (non-final var) | no | false | | `final s="a"; s + "b"` | yes | true | | `(s + "b").intern()` | n/a (runtime+intern) | true |
- Does making the variable final always restore pooling?Only if its initializer is itself a compile-time constant. final String s = "a" folds; final String s = method() does not, so its concatenation is not pooled.
- How is runtime string concatenation implemented in modern Java?Since Java 9, javac emits an invokedynamic bootstrapped by StringConcatFactory, which the JVM links to an efficient concatenation strategy; older versions used explicit StringBuilder.append chains.
saying these in an interview costs you the question
- Claiming any concatenation is pooled
- Saying final always makes a constant (it must also have a constant initializer)
- Believing == is a reliable way to compare string contents
- Thinking runtime concat reuses the pooled literal