skip to content

String Literal Pool and intern()

Compile-time literals are interned into a shared pool, while new String always allocates a distinct object, and intern() looks up or adds the canonical instance. This is the machinery behind the classic == comparison puzzles.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

What is the difference between String s = "hi" and String s = new String("hi") in terms of object creation and identity?

level: juniorimportance: must knowfreq 78%

answer

  1. Literal = pooled, shared, one object
  2. new String() = always a fresh heap object
  3. == compares identity, equals() compares value
  4. "hi" == "hi" true; "hi" == new String("hi") false

basics

~20 s

A string literal like "hi" is stored in a shared pool, so equal literals point to the same object. new String("hi") always makes a brand-new object on the heap, separate from the pool, even if the text is identical.

solid answer

~40 s

A string literal is interned automatically: the compiler/JVM keeps one canonical instance in the string pool, so two identical literals are the same object and compare true with ==. new String("hi") forces allocation of a fresh String on the heap whose char data is distinct from the pooled copy, so literal == new String(...) is false even though equals() is true. The new String constructor is wasteful: it creates an extra object that just duplicates a value already in the pool. That is why "hi" == "hi" is true but "hi" == new String("hi") is false. Always compare strings with equals() for value comparison; reserve == for identity checks. Use literals (or intern) to benefit from sharing; avoid new String() unless you specifically need a distinct instance.

go deeper

for a junior

Knows literals share one object and new String makes a fresh one; uses equals() not == for comparison.

for a middle

Explains == identity vs equals() value, why new String wastes memory, and can predict == results in mixed cases.

for a senior

Discusses the pool's role in memory optimization, when new String is justified (rare), and the link to immutability and security (defensive copies).

for a principal

Frames pooling as a deduplication strategy, weighs it against GC/footprint and JIT escape analysis, and sets team guidance to ban new String() except for documented cases.

## Background: what a String is In Java, `String` is an immutable object: once created, its character contents never change. Because strings are everywhere and immutable, Java optimizes memory by **sharing** identical string values. ## The string pool (a.k.a. string literal pool / intern pool) The **string pool** is a special table the JVM maintains of canonical `String` instances. For every distinct text value in the pool there is exactly **one** `String` object. It lives in normal heap memory (since Java 7; before that it was in the PermGen area). When you write a **string literal** in source code — e.g. `"hi"` — the compiler records it in the class file's constant pool, and at runtime the JVM ensures the pool contains one canonical `String` for that value. Every place in the program that uses the literal `"hi"` gets a reference to that **same** object. ## Two key operators - `==` compares **references** (identity): are these two variables pointing at the *same object in memory*? - `.equals()` compares **values**: do the two strings contain the *same characters*? ## Case 1: `String s = "hi";` This takes a reference to the pooled object. So: ```java String a = "hi"; String b = "hi"; a == b; // true — same pooled object a.equals(b); // true — same value ``` ## Case 2: `String s = new String("hi");` The `new` keyword **always** allocates a fresh object on the heap. The argument `"hi"` is itself a pooled literal, but `new String(...)` copies its characters into a brand-new, separate `String`. So: ```java String a = "hi"; String c = new String("hi"); a == c; // false — different objects a.equals(c); // true — same value ``` `new String("hi")` is essentially wasteful: it creates **two** string-related entities (the pooled literal plus the new copy) when one would do. ## Why this matters 1. **Correctness:** beginners often compare strings with `==` and get surprising results. For value comparison you must use `.equals()`. The literal-vs-new distinction is the classic trap that exposes this bug. 2. **Memory:** literals are shared; `new String` duplicates. In tight loops or large data sets, accidental `new String` allocations waste heap. ## Summary table | Expression | Where the object lives | `== "hi"`? | |---|---|---| | `"hi"` | string pool (shared) | true | | `new String("hi")` | fresh heap object | false | **Rule of thumb:** use literals; use `.equals()` for comparison; only use `new String()` when you deliberately need a distinct instance.

  • How do you make new String("hi") == "hi" return true?
    Call .intern() on it: new String("hi").intern() == "hi" is true, because intern() returns the canonical pooled reference.
  • Are the characters inside new String("hi") shared with the pool?
    No. The new String gets its own copy of the character data, distinct from the pooled literal.

saying these in an interview costs you the question

  • Claiming new String("hi") returns the pooled object
  • Saying == works for string value comparison
  • Thinking literals are also created fresh each time
  • Confusing immutability with pooling — they are related but distinct ideas

context

open as a page

What does the String.intern() method do, and how does it interact with the string pool?

level: middleimportance: should knowfreq 62%

basics

~20 s

intern() looks in the shared string pool for a string with the same value. If one is there, it returns that pooled object; if not, it adds the current string to the pool and returns it. The result is a canonical reference equal-valued strings can share.

open as a page

Which string expressions get pooled at compile time, and why does "a"+"b" == "ab" but s+"b" (with s a variable) does not?

level: seniorimportance: should knowfreq 48%

basics

~20 s

The 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.

open as a page

When is calling intern() a good idea at scale, and what are the risks and alternatives?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

intern() helps when you have many duplicate runtime strings drawn from a small set, because it collapses them to one shared object and saves memory. It hurts when values are mostly unique, because the pool grows, lookups cost time, and GC suffers. Often a plain HashMap cache or JVM string deduplication is safer.

open as a page