Java Boxed Equality
Java wrapper equality depends on whether code compares primitive values, object identity, or object values.
Jul 11, 2026
Java’s == operator has different meanings for primitive and reference operands:
- with primitive numeric values, it compares values;
- with references, it tests whether both operands refer to the same object;
- if one wrapper operand is unboxed by numeric promotion, the resulting primitive values may be compared instead.
This makes wrapper comparisons easy to misread.
Autoboxing And Integer.valueOf
The compiler normally translates:
Integer value = 128;into behavior equivalent to:
Integer value = Integer.valueOf(128);The Java Language Specification requires repeated boxing of certain constant values to produce identical references. This includes booleans, bytes, ASCII-range characters, and integer constant values from -128 through 127.
For values outside the required range, the specification deliberately makes no identity promise. A JVM may cache additional values. Therefore this code is invalid as a value comparison even if it happens to print true on a particular runtime:
Integer a = 128;
Integer b = 128;
System.out.println(a == b);The mistake is depending on wrapper identity, not merely choosing a number above 127.
Comparing Wrappers
When wrappers cannot be null:
a.equals(b)When either value may be null:
Objects.equals(a, b)Unboxing both values is also a value comparison, but throws NullPointerException if either reference is null.
Use == intentionally for:
- primitive comparisons;
- enum constants, whose identity is defined by the enum model;
- testing a reference against
null; - rare cases where object identity itself is the subject of the comparison.
Do not replace every == with .equals() mechanically. Choose the operator that matches the required semantics.
Value Classes
Project Valhalla is developing identity-free value objects. Current Valhalla designs give == state-based semantics for value objects, and primitive wrapper classes may eventually migrate toward that model.
This is preview-stage future work, not the behavior of ordinary identity classes in current production Java. Code should follow the semantics of the Java version it runs on rather than depend on a possible migration.