Why does `==` between two Integer objects sometimes return true and sometimes false?
answer
- == on wrappers = identity, not value
- valueOf caches -128..127 (Integer)
- Small same value -> same object -> == true
- Large value -> new objects -> == false
- Use equals()/Objects.equals or compare as int
basics
~20 s== on two Integer objects compares references, not values. Java caches small boxed Integers (-128 to 127), so two boxes of the same small value share one object and == is true; outside that range you get distinct objects and == is false. Use .equals() or compare as int.
solid answer
~50 s`==` between two reference types compares object identity, not numeric value. Autoboxing routes through `Integer.valueOf`, which keeps a cache of the commonly used range -128..127 (the IntegerCache, lower bound fixed, upper bound tunable). Boxing a value in that range returns the *same* cached object, so `==` is true; boxing a value outside it allocates a *new* object each time, so `==` is false even when the numbers are equal. That's why `Integer a = 100, b = 100; a == b` is true but `Integer a = 200, b = 200; a == b` is false. The correct comparisons are `a.equals(b)` for value equality on wrappers, or unbox to primitives (`a.intValue() == b.intValue()`, or compare with one primitive operand so the other is unboxed). Long/Short/Byte/Character have analogous caches; Boolean caches both values. Relying on `==` for wrapper value equality is a real-world bug.
go deeper
Knows to use .equals() rather than == for Integer value comparison.
Explains the reference-vs-value distinction and the -128..127 cache, and can predict the true/false outputs for 100 vs 200.
Identifies the latent production bug (passes for small values, fails ≥128), prescribes Objects.equals/primitive comparison, and knows which wrappers cache.
Treats it as a correctness-by-default concern: bans == on wrappers via lint/static analysis and explains the immutability/optimization rationale to the team.
## Two different meanings of `==` - For **primitives**, `==` compares values: `5 == 5` is true. - For **reference types** (objects), `==` compares *references* — whether both variables point to the **same object in memory**, not whether the objects are 'equal'. `Integer` is a reference type, so `==` between two `Integer`s asks 'are these the same object?'. ## Where the cache comes from Autoboxing does not call a constructor; it calls `Integer.valueOf(int)`. That method maintains a small **IntegerCache**: it pre-builds and reuses `Integer` objects for the range **-128 to 127** (the low end is fixed by the spec; the high end can be raised with `-XX:AutoBoxCacheMax`/`java.lang.Integer.IntegerCache.high`). So: ```java Integer a = 100, b = 100; // both come from the cache -> same object System.out.println(a == b); // true Integer c = 200, d = 200; // outside cache -> two new objects System.out.println(c == d); // false ``` The value comparison, however, is always correct: ```java System.out.println(a.equals(b)); // true System.out.println(c.equals(d)); // true ``` ## Why the cache exists Small integers are extremely common, so reusing immutable objects for them saves allocations. The visible identity behavior is a *side effect* of that optimization, not a feature to depend on. `Integer` is immutable, so sharing is safe. ## The general rule - **Never** use `==` to compare wrapper values. Use `equals()`, or convert to primitives. - When one operand is a primitive, `==` unboxes the other and does a value comparison — that is safe: `someInteger == 200` (where 200 is an `int` literal) compares values. - The same caching applies to `Long`, `Short`, `Byte` (all -128..127), `Character` (0..127), and `Boolean` (both values cached). `Float`/`Double` have **no** cache. ## The bug pattern in the wild ```java Map<String,Integer> counts = ...; if (counts.get("a") == counts.get("b")) { ... } // identity, not value! ``` Works 'by luck' for small counts (cached, same object) and silently breaks for counts ≥ 128. The fix is `Objects.equals(counts.get("a"), counts.get("b"))` (also null-safe) or comparing unboxed ints. ## Takeaway `==` on boxed numbers is identity; the cache makes it *accidentally* work for -128..127, which is the worst kind of bug — it passes your small-value tests and fails in production on large values.
- Is `Integer x = 1000; if (x == 1000)` safe?Yes. The literal 1000 is a primitive int, so x is unboxed and a value comparison is done. The danger is only `==` between two wrappers.
- Can you change the cache range?You can raise the upper bound via `-XX:AutoBoxCacheMax=<n>` or the `java.lang.Integer.IntegerCache.high` property; the lower bound -128 is fixed. Doing so to 'fix' == is a smell — use equals instead.
saying these in an interview costs you the question
- Concluding `==` works for Integer because a small-value test passed
- Thinking the cache makes large values share objects too
- Forgetting Float/Double have no cache
- Using `==` in production code to compare boxed counts/ids