Skip to content
Part IA Michaelmas Term

Autoboxing and Auto-Unboxing

Primitives and Their Wrappers

Java has eight primitive types. Each has a corresponding wrapper class in java.lang:

PrimitiveWrapper
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Wrapper objects are needed wherever an Object or generic type is required — collections, generic method arguments, reflection, etc. Primitive values cannot be stored directly in a List — you must wrap them:

List<Integer> numbers = new ArrayList<>();
numbers.add(42);         // autoboxing: int → Integer
int x = numbers.get(0);  // auto-unboxing: Integer → int

How Autoboxing/Unboxing Works

The compiler silently inserts calls to the wrapper’s conversion methods:

Integer boxed = Integer.valueOf(42);   // manual boxing
int unboxed = boxed.intValue();        // manual unboxing

The same conversions happen implicitly in the compiled bytecode for autoboxing:

Integer boxed = 42;      // compiler emits: Integer.valueOf(42)
int unboxed = boxed;     // compiler emits: boxed.intValue()

The Null Trap

Auto-unboxing a null wrapper throws NullPointerException:

Integer count = null;
int x = count;  // NullPointerException — compiler emits count.intValue() on null

This is a surprisingly common bug. A method returning Integer may return null to indicate “no value”, and calling code that assigns the result to int blows up. Always check for null before unboxing, or use Optional<Integer> to make the absence explicit.

The Integer Cache

Integer.valueOf(int) caches values from 128-128 to 127127 (by default). Autoboxing uses valueOf(), so small integers reuse cached objects:

Integer a = 100;  // Integer.valueOf(100) → cached object
Integer b = 100;  // Integer.valueOf(100) → same cached object
System.out.println(a == b);  // true — same object from cache

Integer x = 200;  // Integer.valueOf(200) → new object (outside cache range)
Integer y = 200;  // Integer.valueOf(200) → different new object
System.out.println(x == y);  // false — different objects

This is a trap. == appears to work for small numbers but fails for large ones. Always use .equals() for wrapper comparison. The cache is an implementation optimisation, not a semantic guarantee.

Performance Implications

Autoboxing creates temporary wrapper objects. In tight loops, this can cause significant GC pressure:

Integer sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;  // unbox sum, add i, box result — 3 operations per iteration
}

Each iteration unboxes sum, adds a primitive i, and boxes the result — creating up to a million temporary Integer objects that the GC must collect. The int version is dramatically faster:

int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;  // simple integer addition — no object allocation
}

In numeric-heavy code, prefer primitives and primitive arrays over boxed collections.

Wrapper Utility Methods

The wrapper classes also provide static utility methods:

int x = Integer.parseInt("42");           // string → int
String s = Integer.toString(42);          // int → string
Integer i = Integer.valueOf("42");        // string → Integer (boxed)
boolean isNaN = Double.isNaN(0.0 / 0.0);  // check for NaN
boolean isDigit = Character.isDigit('5'); // character classification
int cmp = Integer.compare(a, b);          // safe comparison (no overflow)

For parsing, prefer Integer.parseInt(s) over Integer.valueOf(s).intValue() to avoid unnecessary boxing.

Wrapper Immutability

All wrapper objects are immutable. Once an Integer is created with a particular value, that value can never change. This is why sum += i creates a new Integer each time — the operator produces a new object rather than mutating the existing one. Immutability makes wrappers safe to share (like String), but it also contributes to the allocation overhead in loops.