Question: How does intern() work?
Answer: intern() asks the JVM for the canonical version of a string, meaning the one shared instance the runtime keeps for that text. If an equal string is already in the string pool, Java returns that existing object; otherwise, it adds the string to the pool and returns it. This is why == can become true for interned strings even though it usually should not be used for string content checks.
Interview-Ready Answer: In Java, intern() returns the pooled, canonical String for the same character sequence. If the pool already contains that text, I get the existing reference; if not, the JVM stores it in the pool and returns it. The practical payoff is that identical strings can share one object, but I should still use equals() for normal content comparison because intern() has overhead and is only worth using in special memory or identity-based cases.
Detailed Explanation: The string pool is a JVM-managed cache of string values. A canonical string is the one shared object chosen to represent a particular sequence of characters. intern() is the method that asks for that shared object.
s.intern().StringTable in HotSpot.You use intern() when you have many repeated strings and you care about memory or identity sharing. A classic example is a parser, tokenizer, compiler, or dedup-heavy domain where the same values repeat thousands or millions of times. In everyday business code, it is usually unnecessary because equals() is enough and simpler.
| Approach | What you get | Best use |
|---|---|---|
equals() | Content check | Normal string comparison |
== | Same object | Only identity tests |
intern() | Pooled shared object | Heavy duplication cases |
Lookup is typically close to O(1) average time because the pool uses hashing, but it is not free. Interning has extra work: hashing, lookup, possible insertion, and more pressure on the pool. Space can improve if many duplicates collapse into one object, but if most strings are unique, intern() can waste time with little gain.
null does not have an intern() method call in practice; calling it on a null reference throws NullPointerException.intern() to make all string comparisons faster. It can actually slow code down if overused.Memory Hook: Think of the string pool like a library of name tags: intern() asks, “Do we already have this exact name tag?” If yes, reuse it; if not, print one and file it for later.
Real-World Story: Imagine a log ingestion service for a large chat app. Every message has repeated values like country codes, event types, and feature flags. The team interns a small set of high-repeat strings so that millions of identical values do not create millions of separate objects, which helps reduce heap usage and GC work.
What goes wrong when someone misunderstands it? A developer uses == on non-interned strings and gets random-looking false results, so messages are misrouted or deduplication fails. In production, you might see logs like routing key mismatch, repeated database inserts, or memory growth because the code interns too many unique user-generated strings. Users notice delays, and the GC logs show more frequent pauses because the heap is full of strings that should never have been interned in the first place.
import java.util.Objects;
public class InternDemo {
public static void main(String[] args) {
// String literals are already in the string pool.
String literal = "hello";
// new String(...) creates a distinct object on the heap, even if the text is the same.
String heapString = new String("hello");
// intern() asks the JVM for the pooled, canonical version.
String pooled = heapString.intern();
System.out.println("literal == heapString : " + (literal == heapString));
System.out.println("literal == pooled : " + (literal == pooled));
System.out.println("heapString.equals(literal) : " + heapString.equals(literal));
// Dynamic strings are a common reason to consider interning.
String part1 = new String("he");
String part2 = "llo";
String dynamic = (part1 + part2); // built at runtime, so not automatically pooled
String dynamicInterned = dynamic.intern();
System.out.println("dynamic.equals(literal) : " + dynamic.equals(literal));
System.out.println("dynamic == literal : " + (dynamic == literal));
System.out.println("dynamicInterned == literal : " + (dynamicInterned == literal));
// Edge case: calling a method on null throws NullPointerException.
try {
String nullRef = null;
nullRef.intern();
} catch (NullPointerException ex) {
System.out.println("null.intern() throws NPE as expected: " + ex.getClass().getSimpleName());
}
// Defensive check if you accept user input.
String maybeNull = null;
String safe = internOrNull(maybeNull);
System.out.println("safe value is null : " + (safe == null));
}
private static String internOrNull(String value) {
// This helper shows the right pattern when null is possible.
return value == null ? null : value.intern();
}
}Follow-up & Tricky Questions:
equals() and == for strings?equals() compares text content; == compares object identity. Interning can make == appear to work, but only because both references point to the same pooled object.intern() create a new string?intern()?equals().intern() thread-safe?intern() change the string’s content?intern() fix bad string comparison code?equals() for value comparison and reserve == for identity checks.intern() guarantee the same reference for all equal strings?equals() is already efficient and much clearer.Common Mistakes:
== for normal comparison. Correction: use equals() unless you specifically want object identity.intern() always improves performance. Correction: it adds work and only helps when many equal strings repeat.Memory Hook: “intern() is the librarian.” It checks whether the same book title already exists on the shelf; if it does, you borrow that copy, and if not, the librarian adds one for everyone to share.
Cheat Sheet:
intern() returns the canonical pooled string.equals() checks content; == checks identity.intern() only for repeated, high-duplication strings.Practice Tasks:
new String(), and concatenation, then print == and equals() results before and after intern().