Why can == sometimes return true and sometimes false when comparing two Integer objects with the same value?
answer
- Integer is an object -> == is reference identity
- Cache -128..127: same-value boxes are == in range, not outside
- Compare wrappers with .equals() or unbox to int
- Wrapper == primitive triggers unboxing (value compare, but NPE if null)
- Float/Double never cache; Long/Short/Byte/Char do
basics
~20 sInteger is an object, so == compares references, not the number. Java caches small boxed values (-128 to 127), so two boxed ints in that range can share one object and be == ; outside that range they are different objects and == is false. Always use .equals() or unbox to int.
solid answer
~40 sInteger is a wrapper object, so == compares object references, not the numeric value. The surprise comes from the integer cache: autoboxing via Integer.valueOf returns cached, shared instances for values in -128..127 (configurable upper bound), so two boxed values in that range are the same object and == is true. Box a value outside that range (e.g. 1000) and each box is a fresh object, so == is false even though the numbers are equal. The fix is to compare with .equals(), or unbox to a primitive int (a == b where at least one side is an int triggers unboxing and value comparison). This also intersects with NullPointerException risk: unboxing a null Integer throws, so == against a primitive can blow up. Treat == on wrappers as a code smell.
code
java · 12 linesInteger a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println(a == b); // true: cached (-128..127)
System.out.println(c == d); // false: outside cache, distinct objects
System.out.println(c.equals(d)); // true: value comparison
int primitive = 128;
System.out.println(c == primitive);// true: c is unboxed to int
Integer n = null;
// System.out.println(n == primitive); // NullPointerException on unboxinggo deeper
May only know to use .equals() for objects; the cache nuance is above this level.
Knows Integer == compares references and that .equals() or unboxing is the fix.
Explains the -128..127 cache, the wrapper-vs-primitive unboxing behavior, and the null-unboxing NPE.
Discusses why autoboxing in hot paths or as map keys is a design risk, the JVM cache tunable, and steering APIs toward primitives or value-based comparisons.
## The setup: wrappers and autoboxing Each primitive has a **wrapper class** — `int`/`Integer`, `long`/`Long`, `boolean`/`Boolean`, etc. A wrapper is a full **object** on the heap. **Autoboxing** is the compiler automatically converting a primitive to its wrapper (`Integer x = 5;` really calls `Integer.valueOf(5)`), and **unboxing** is the reverse (`int y = x;` calls `x.intValue()`). ## Why == surprises you Because an `Integer` is an object, `==` compares **references** (identity), not the wrapped number. So whether `==` is true depends on whether the two boxes are the *same object*. ## The Integer cache The JLS requires `Integer.valueOf` to **cache** boxed values in the range **-128 to 127** (the upper bound is tunable via `-XX:AutoBoxCacheMax` / `java.lang.Integer.IntegerCache.high`). Within that range, autoboxing the same value returns the **same shared instance**: ```java Integer a = 100, b = 100; System.out.println(a == b); // true — both from the cache Integer c = 1000, d = 1000; System.out.println(c == d); // false — two separate objects ``` This is why the *same code pattern* flips behavior depending on the magnitude — a notorious gotcha. `Boolean`, `Byte`, `Character` (0–127), `Short` and `Long` (-128..127) cache too; `Float`/`Double` never cache. ## The correct ways to compare 1. **`.equals()`** — `a.equals(b)` compares values regardless of caching. 2. **Unbox to primitive** — if at least one operand is a primitive `int`, the other is unboxed and the comparison becomes a value comparison: ```java Integer c = 1000; int p = 1000; System.out.println(c == p); // true — c is unboxed to int ``` 3. **`Objects.equals` / `Integer.compare`** for null-tolerant or relational needs. ## The hidden NullPointerException Unboxing a `null` wrapper throws NPE. So: ```java Integer maybe = null; int p = 5; // maybe == p; // unboxes maybe -> NullPointerException ``` A `==` between a wrapper and a primitive silently introduces an unboxing that can NPE — another reason to be deliberate. ## Relational operators on wrappers `<`, `<=`, `>`, `>=` on `Integer` operands **unbox** both sides and compare values (there is no identity option for relational), which is usually what you want — but it shares the same null-unboxing NPE risk. ## Why it matters This is a senior-level trap because it passes naive tests (small literals land in the cache) and fails in production (real-world values exceed 127). The rule: never use `==` to compare wrapper *values* — use `.equals()` or work in primitives.
- What is the guaranteed Integer cache range and can it change?-128 to 127 is guaranteed by the JLS. The upper bound can be raised via -XX:AutoBoxCacheMax or the java.lang.Integer.IntegerCache.high property, but you should never rely on caching for correctness.
- How can a simple Integer == comparison cause a NullPointerException?If one side is a primitive int, the Integer is unboxed via intValue(). If that Integer is null, unboxing throws NPE before any comparison happens.
saying these in an interview costs you the question
- Concluding Integer == always works because it worked for small numbers
- Forgetting that unboxing a null wrapper throws NPE
- Thinking the cache range is part of the language guarantee for all values (only -128..127 is guaranteed)