~/notes/java-boxed-equality

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:

java
Integer value = 128;

into behavior equivalent to:

java
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:

java
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:

java
a.equals(b)

When either value may be null:

java
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.

Further Reading