skip to content

Why does comparing two Integer values with == give true for 127 but false for 128?

level: middleimportance: must knowfreq 85%

answer

  1. valueOf caches -128..127
  2. == is identity, not value
  3. 128 -> two new objects -> false
  4. AutoBoxCacheMax tunes the upper bound
  5. .equals or unbox to compare

basics

~20 s

Integer.valueOf caches the small values -128..127, so autoboxing 127 twice returns the same object and == is true. 128 is outside the cache, so two new objects are created and == compares references, giving false. Always use .equals for wrappers.

solid answer

~40 s

When you autobox an int, the compiler calls Integer.valueOf, which keeps a shared cache of the boxed values from -128 to 127 (the upper bound is tunable via -XX:AutoBoxCacheMax). For 127, both autoboxings return the exact same cached Integer object, so == (a reference identity check) is true. For 128, valueOf allocates a fresh Integer each time, so the two references differ and == is false even though the numeric values are equal. The lesson: == on wrapper references tests object identity, not numeric value. Always compare wrappers with .equals(), or unbox to primitives first (e.g. compare int to int). The same caching applies to Long, Short, and Byte (-128..127), Character (0..127), and Boolean (TRUE/FALSE); Float and Double are never cached. This is one of the most common Java interview traps.

code

java · 8 lines
java
Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println(a == b);        // true  (cached, same object)
System.out.println(c == d);        // false (two new objects)
System.out.println(c.equals(d));   // true  (value equality)

int e = 128;
System.out.println(c == e);        // true  (c is unboxed: value compare)

go deeper

for a junior

Knows you should compare wrappers with .equals, not ==, and that == can behave oddly.

for a middle

Explains the -128..127 cache via valueOf and reference vs value identity, and predicts the 127 true / 128 false result.

for a senior

Adds the tunable upper bound (-XX:AutoBoxCacheMax), which types cache, and the mixed-operand unboxing rule; recommends primitives or .equals.

for a principal

Treats it as a correctness/footgun policy: ban == on wrappers via static analysis (e.g. ErrorProne ReferenceEquality), reason about cache config portability across deployments.

## The setup Consider: ```java Integer a = 127, b = 127; Integer c = 128, d = 128; System.out.println(a == b); // true System.out.println(c == d); // false ``` Many people expect both to be `true` (the numbers are equal) or both `false`. The split result surprises them. To understand it you need three facts. ## Fact 1: == on references compares identity, not value For object references, `==` asks 'are these the *same object* in memory?' — not 'do they hold equal values?'. `Integer` is a class, so `Integer a` and `Integer b` are references. `a == b` is true only if both point at the *one and the same* `Integer` instance. ## Fact 2: autoboxing goes through Integer.valueOf The literal assignment `Integer a = 127;` is autoboxing; the compiler rewrites it to `Integer a = Integer.valueOf(127);`. It does **not** call `new Integer(...)`. This matters because `valueOf` is allowed to return a shared object. ## Fact 3: valueOf caches a small range `Integer.valueOf` maintains an internal cache (the `IntegerCache`) of pre-created `Integer` objects for every value from **-128 to 127** inclusive. When you box a value in that range, `valueOf` returns the *same cached object* every time. Outside the range it does `new Integer(value)` (a fresh object) on each call. So: - `valueOf(127)` returns the cached object both times -> `a` and `b` are the **same** reference -> `a == b` is **true**. - `valueOf(128)` allocates two different objects -> `c` and `d` are **different** references -> `c == d` is **false**. ## Why -128..127? And can it change? The lower bound (-128) is fixed by the JLS. The **upper bound is tunable**: the JVM flag `-XX:AutoBoxCacheMax=<N>` (or the property `java.lang.Integer.IntegerCache.high`) raises the top of the cache, so with a high enough value even `128 == 128` could become true. This is why you must **never** rely on `==` either way — the behavior is implementation/config dependent. The range was chosen because small integers are by far the most common (loop counters, small ids), so caching them saves a lot of allocations. ## Which types cache - `Integer`, `Long`, `Short`, `Byte`: cache **-128..127** (Integer's upper bound tunable). - `Character`: caches **0..127**. - `Boolean`: only the two singletons `Boolean.TRUE` / `Boolean.FALSE`. - `Float`, `Double`: **never** cached (a continuous range can't be enumerated). ## Mixed comparisons unbox There is a subtlety: if **one** operand of `==` is a primitive, the wrapper is *unboxed* and a numeric comparison happens. So `Integer c = 128; int e = 128; c == e` is **true** — comparing object-to-primitive forces value comparison. The trap only bites when **both** sides are wrapper references. ## The fix For wrappers, compare with **`.equals()`** (value equality) or unbox at least one side to a primitive. `a.equals(b)` is always true when the numbers match, regardless of caching. In modern code, prefer primitives for arithmetic and identity, and reserve wrappers for collections/nullability.

  • How would you make 128 == 128 print true for two Integers?
    Raise the cache upper bound at JVM start with -XX:AutoBoxCacheMax=128 (or -Djava.lang.Integer.IntegerCache.high=128). But you should not rely on this in code; use .equals instead.
  • Does the trap apply when one side is a primitive int?
    No. If either operand is a primitive, the wrapper is unboxed and a numeric value comparison happens, so it returns true whenever the values match.

The cache is like a shop keeping pre-printed name tags for the 256 most common names. Ask twice for 'Bob' and you get the identical tag; ask for a rare name and the shop prints a brand-new one each time.

saying these in an interview costs you the question

  • Saying == always works for Integers because Java compares values
  • Believing the cache covers all ints
  • Claiming Double/Float are cached too
  • Thinking the upper bound is fixed and cannot change
  • Using == to compare wrappers in production code

context