Can You Use == to Compare Strings?
Technically — yes, but in 99% of cases it will be a mistake.
🟢 Junior Level
Technically — yes, but in 99% of cases it will be a mistake.
The == operator compares references (memory addresses), not string content.
Example of the problem:
String s1 = "Hello";
String s2 = new String("Hello");
System.out.println(s1 == s2); // false — different objects!
System.out.println(s1.equals(s2)); // true — content is the same
Rule: For string content comparison always use equals(). The == operator — only for null checks.
When == will work:
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true — both from String Pool, same object
But you can’t rely on this — data from DB or files will be in regular heap, not in the pool.
🟡 Middle Level
When == returns true
| Scenario | Code | == Result |
|---|---|---|
| Two literals | "A" == "A" |
true (String Pool) |
| Literal + new | "A" == new String("A") |
false |
| new + new | new String("A") == new String("A") |
false |
| Assignment | s1 = s2; s1 == s2 |
true (same reference) |
| After intern() | s1.intern() == s2.intern() |
true |
Typical mistakes
-
Mistake:
if (str == "admin")— works only if"admin"is from pool andstris too Solution:if ("admin".equals(str))— safe and with constant on the left (null-safe) -
Mistake: Comparing strings from DB via
==Solution: Strings from ResultSet — always new objects. Onlyequals()
Practical rule
// BAD
if (status == "ACTIVE") { ... }
// GOOD
if ("ACTIVE".equals(status)) { ... }
// BETTER (null-safe)
if (Objects.equals(status, "ACTIVE")) { ... }
🔴 Senior Level
Internal Implementation
Operator ==:
- JVM instruction:
if_acmpeq(if address compare equal) - Compares reference values (32/64-bit addresses)
- Time: O(1) — single pointer comparison
String.equals():
public boolean equals(Object anObject) {
if (this == anObject) return true; // Optimization: identity check
if (anObject instanceof String another) {
if (coder == another.coder) { // Same encoding?
return isLatin1()
? StringLatin1.equals(value, another.value)
: StringUTF16.equals(value, another.value);
}
}
return false;
}
- Identity check (==) as fast path
- Then: coder check, length check, character-by-character comparison
- Time: O(n) — character-by-character iteration
Architectural Trade-offs
When == may be justified:
In ultra-low-latency systems (HFT, trading), where every nanosecond matters:
// All strings pre-interned
String eventType = parseEvent().intern();
if (eventType == ORDER_EVENT) { // O(1) instead of O(n)
processOrder();
}
- Profit: 1 CPU cycle vs n cycles (string length)
- Risk: one non-interned string — and logic is broken
Why this is almost never worth the risk:
equals()has a fast path==— for identical objects returns instantly- JVM inlines
equals()for short strings - CPU branch prediction works well with character-by-character comparison
Edge Cases
-
Constant folding:
"A" + "B" == "AB"→true(compiler folds both into"AB") - Runtime concatenation:
String a = "A"; String ab = a + "B"; System.out.println(ab == "AB"); // false — runtime concatenation - Interned strings:
String s1 = new String("test").intern(); String s2 = "test"; System.out.println(s1 == s2); // true — both from pool - String deduplication (G1 GC): Does NOT make
==true! Deduplication combinesbyte[], butStringobjects remain different.
Performance
| Operation | Time | When to use |
| ————————— | ————————– | ————————————– |
| == | ~0.3ns (1 CPU cycle) | Only after guaranteed intern() |
| equals() (identical) | ~0.3ns (fast path) | Always — fast path catches identity |
| equals() (same content) | ~2-10ns (depends on length)| Always — correct choice |
equals()for short strings is optimized by JVM via SIMD instructions (comparing 8-16 bytes per clock cycle), so the difference from==is minimal.
Production Experience
Scenario: Protocol parser (10M messages/sec):
- Developer used
==to compare event types afterintern() - After a month: a new library version returned non-interned strings
- Bug: 0.01% of messages processed incorrectly → financial loss
- Fix:
equals()— overhead 5ns on 10M = 50ms/sec (acceptable)
Monitoring
// Assert that strings are interned (in tests)
assert s1 == s2 : "Strings should be interned";
// JMH for benchmarking
@Benchmark
public boolean testEquals() { return s1.equals(s2); }
@Benchmark
public boolean testIdentity() { return s1 == s2; }
Best Practices for Highload
- Default: always
equals()orObjects.equals() - null-check:
"CONSTANT".equals(variable)orObjects.equals(a, b) - Low-latency edge case:
==is acceptable in two cases: (1) null check, (2) guaranteed interned strings.- There are unit tests verifying this guarantee
- Benchmark shows the difference is critical
- For comparison with
null:==is the correct choice
🎯 Interview Cheat Sheet
Must know:
==compares references (addresses),equals()— content==returns true for literals ("A" == "A"), but false fornew String("A")equals()has a fast path: if==is true — returns immediately without character comparison- After
intern()both strings are from pool —==returns true - String deduplication (G1 GC) does NOT make
==true — objects are different, onlybyte[]is shared - In ultra-low-latency systems
==is acceptable for guaranteed interned strings
Frequent follow-up questions:
- When does
==return true? — For literals, assignments (s1 = s2), afterintern(), constant folding ("A"+"B" == "AB"). - Why does
equals()have a fast path==? — If it’s the same object — no need to compare content. Saves time for identical references. - Can you use
==afterintern()? — Yes, but it’s risky: one non-interned string — and logic is broken.equals()is safer. - What happens with
runtime concatenation == literal? —false.String a = "A"; (a + "B") == "AB"— runtime concatenation creates a new object.
Red flags (DON’T say):
- ❌ “
==compares content” — compares only references - ❌ “
equals()is always slower than==” — for identical objectsequals()===via fast path - ❌ “String deduplication makes
==true” — no, objects remain different - ❌ “
==works after concatenation” — runtime concatenation creates a new object
Related topics:
- [[10. Difference Between == and equals() for String]]
- [[1. How String Pool Works]]
- [[3. When to Use intern()]]