Java String Pool
Table of Contents + β
In the last lesson you learned an introduction to Strings. Now letβs clear up something that confuses almost every learner. Why does == on two equal-looking Strings sometimes say true and sometimes false? The answer is a memory trick called the String pool.
π€ The puzzle
Here is the pain:
- You compare two Strings that clearly hold the same words.
- Sometimes
==says they match, sometimes it says they do not. - You cannot trust your own eyes.
Try to guess the output before reading on.
String a = "hello";String b = "hello";String c = new String("hello");
System.out.println(a == b); // ?System.out.println(a == c); // ?System.out.println(a.equals(c)); // ?All three hold βhelloβ, yet the results differ. To see why, you need two ideas: what == really checks, then what the String pool does.
Output
truefalsetrueπ What == really checks
For objects like Strings, == does not look at the text. Think of it like home addresses:
==asks βdo you both live at the same address?β It compares references (the memory address a variable points to). Same object meanstrue..equals()asks βdo your houses look the same inside?β It compares the actual characters. Same text meanstrue.
So when you want to know βis the text the same?β, reach for .equals(). But why would two separate "hello" Strings ever be the same object? That is the pool.
π What is the String pool?
The String pool is a special area inside heap memory that stores String literals (text you write in double quotes, like "hello"). Java keeps one copy of each unique literal, so writing "hello" again hands you the existing one.
Why Java does this:
- It saves memory. One shared copy of
"yes"or"error"instead of thousands adds up fast. - It is safe to share. Strings are immutable, so the shared copy can never change. Nothing can edit
"hello"and break another part of the program.
That second point is the quiet hero: pooling is only safe because nothing can mutate the shared value.
String a = "hello"; // creates "hello" in the poolString b = "hello"; // β
reuses the SAME pooled "hello"System.out.println(a == b); // true: same objectBoth a and b point to the one shared "hello". So == is true. Not luck. The pool deliberately shares one copy.
Output
trueπΊοΈ A picture of the memory
A map makes it click. The heap holds your objects. Inside it sits a fenced-off section, the String pool, that holds the literals.
Follow the arrows:
aandbpoint to the same"hello"inside the pool, soa == bistrue.cpoints to a different box outside the pool, soa == cisfalse.- The text matches in all three, so
.equals()istruefor every pair.
π What new String() does differently
new String("hello") is a direct order: build a brand new object, separate from the pooled one, even though the text is identical. The word new always means βbuild a fresh object right nowβ.
String a = "hello"; // β
from the poolString c = new String("hello"); // β a new, separate object
System.out.println(a == c); // false: different objectsSystem.out.println(a.equals(c)); // true: same textLine by line:
apoints to the pooled"hello", the shared one.cpoints to a fresh objectnewcreated in the heap.a == cisfalsebecause they sit at different addresses.a.equals(c)istruebecause the characters are identical.
The lesson: always use .equals() for text. With == the answer depends on which object you happen to hold.
Output
falsetrueOne more sneaky case. Strings built at runtime, not as plain literals, also skip the pool.
String a = "hello";String d = "hel";String e = d + "lo"; // built at runtime from a variable
System.out.println(a == e); // β false: e is a new runtime objectSystem.out.println(a.equals(e)); // β
true: same textEven though e spells βhelloβ, Java joined pieces while running, so the result is a new object, not the pooled literal:
==saysfalsehere too.- This is the trap with user input and concatenation. The text looks right, but
==still fails.
Output
falsetrueNever compare String text with ==
Comparing String text with == is a classic bug. It may work by accident with pooled literals, then break the moment you use new, read user input, or join Strings at runtime. Always use .equals() (or equalsIgnoreCase) when you care about the text.
π§ The intern() method
Literals go into the pool for free. But what about a String that is not pooled, like one from new or user input? The intern() method returns the pooled copy of a Stringβs text, adding it first if needed.
String c = new String("hello");String pooled = c.intern(); // get the pooled "hello"String a = "hello";
System.out.println(c == pooled); // false: c is the new object, pooled is the shared oneSystem.out.println(a == pooled); // true: both are the pooled objectWalk through it:
cis the fresh object fromnew, outside the pool.c.intern()returns the shared pooled copy, stored inpooled.ais the literal, already pointing to that same pooled copy.a == pooledistrue; both point to the one pooled object.c == pooledisfalse;intern()does not changec, it just returns the pooled one.
You rarely need intern() in everyday code. It is for special cases with huge numbers of repeated Strings.
Output
falsetrueβ οΈ Common Mistakes
A few String-pool slip-ups trip up almost everyone:
- Using
==to compare text. It checks object identity, not the characters. - Assuming
new String()gives the pooled object. It does not. - Thinking the pool covers runtime-built Strings. Strings joined with
+from variables, or read from input, are usually not pooled.
Wrong way and right way side by side:
String name = getUserName(); // text comes from input
// β works by luck sometimes, fails oftenif (name == "Alex") { ... }
// β
compares the actual text every timeif (name.equals("Alex")) { ... }β Best Practices
Good habits around the String pool keep these bugs out for good:
- Always compare text with
.equals(). Never depend on==for String contents. - Put the literal first to dodge null crashes. Write
"Alex".equals(name)so a nullnamedoes not crash. - Prefer literals over
new String(). Literals use the pool;new String("hello")wastes memory for no benefit. - Use
equalsIgnoreCasewhen capital letters should not matter. It treats βYESβ and βyesβ as the same. - Do not reach for
intern()by habit. Use it only after measuring a real memory problem from repeated Strings.
π§© What Youβve Learned
The == mystery is solved. Recap:
- β The String pool is a special area inside heap memory that stores String literals and reuses one shared copy of each unique text to save memory.
- β Sharing is safe because Strings are immutable, so the shared copy can never change.
- β
==compares object identity (same memory address), while.equals()compares the actual text. - β
Two identical literals share the pooled object, so
==is oftentruefor them. - β
new String("...")and runtime-built Strings make separate objects, so==isfalseeven when the text matches. - β
intern()returns the pooled copy of a String on demand. - β
Always compare String text with
.equals(), never==.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does == check when used on two String variables?
Why: For objects, == checks reference identity, not the text. Use .equals() for text.
- 2
Why is a == b often true for two String literals with the same text?
Why: The String pool stores one copy of each literal, so identical literals point to the same object.
- 3
What does new String("hello") create compared to the literal "hello"?
Why: new String forces a new, distinct object even though the text is identical, so == is false.
- 4
How should you always compare the text of two Strings?
Why: .equals() compares the actual characters and is reliable regardless of pooling.
π Whatβs Next?
You now understand how Strings live in memory and why == behaves the way it does. Up next we explore the many built-in methods Strings give you for searching, changing, and slicing text.