JavaAutoboxing & Unboxing

Autoboxing & Unboxing

Wrapper classes let primitives participate in the object-oriented parts of Java, but constantly writing Integer.valueOf(5) by hand would be tedious. Java solves this with autoboxing and unboxing — automatic conversions the compiler inserts for you.
What Is Autoboxing?

Autoboxing is the compiler automatically converting a primitive value into its corresponding wrapper object wherever an object is required. You write ordinary-looking code with primitives, and the compiler quietly wraps them for you.

Autoboxing in action

Java
import java.util.List;
import java.util.ArrayList;

public class AutoboxingDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        numbers.add(10); // int 10 is autoboxed to Integer.valueOf(10)
        numbers.add(20);
        numbers.add(30);

        System.out.println(numbers);
    }
}
[10, 20, 30]
The list requires Integer objects, but we passed plain int literals. The compiler rewrote each .add(10) call behind the scenes as though we had written .add(Integer.valueOf(10)).
What Is Unboxing?

Unboxing is the reverse process — the compiler automatically extracts the primitive value out of a wrapper object wherever a primitive is expected.

Unboxing in action

Java
import java.util.List;
import java.util.ArrayList;

public class UnboxingDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(10);
        numbers.add(20);

        Integer boxedFirst = numbers.get(0);
        int total = boxedFirst + numbers.get(1); // unboxed to add as ints

        System.out.println(total);
    }
}
30
The Classic Trap: Unboxing null
Unboxing calls a method like .intValue() on the wrapper object behind the scenes. If that wrapper reference is null, there is no object to call a method on, and the result is a NullPointerException — one of the most common real-world bugs involving wrapper classes.

A broken example

Java
public class UnboxingNpeDemo {
    public static void main(String[] args) {
        Integer count = null; // e.g. never set from a database lookup

        int total = count + 1; // unboxing null throws NPE!

        System.out.println(total);
    }
}
Warning
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "count" is null. The + operator needs a primitive int, so Java tries to unbox count — but there is no int inside a null reference to unbox. Always check a wrapper object for null before using it in a primitive context, especially when the value came from a map, a database column, or an optional field.
Performance Cost in Tight Loops

Autoboxing is convenient, but it is not free. Every time a primitive is boxed, Java may need to allocate a new wrapper object on the heap. In a loop that runs millions of times, this can create millions of short-lived objects, adding real overhead and extra pressure on the garbage collector.

Autoboxing inside a hot loop

Java
public class BoxingOverheadDemo {
    public static void main(String[] args) {
        Long sum = 0L; // wrapper type instead of primitive long

        for (long i = 0; i < 1_000_000; i++) {
            sum += i; // unbox sum, add, re-box the result — every iteration
        }

        System.out.println(sum);
    }
}
Warning
Because sum is declared as Long instead of long, every single iteration unboxes the current sum, performs the addition as a primitive, and boxes a brand new Long object to store the result. Declaring sum as a plain long avoids all of that allocation and runs significantly faster.
Tip
Prefer primitives for loop counters, accumulators, and any performance-sensitive numeric work. Reserve wrapper types for places that genuinely need object semantics — generic collections, nullable fields, and API boundaries that expect objects.
The Integer Cache Gotcha
For performance reasons, Java pre-creates and caches Integer objects for values from -128 to 127. When autoboxing produces a value in that range, Java reuses one of these cached objects instead of creating a new one. Outside that range, a brand new object is created every time. This has a surprising side-effect on == comparisons.

Same code, different results

Java
public class IntegerCacheDemo {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b); // true — both from the cache

        Integer x = 200;
        Integer y = 200;
        System.out.println(x == y); // false — two separate objects!
    }
}
true
false
Note
== on wrapper objects compares references (are these the exact same object?), not values. Values 100 and 100 both come from the shared Integer cache, so a == b is true. Values 200 and 200 fall outside the cached range, so each autoboxing creates a distinct object, making x == y false — even though both hold the same numeric value. This is a genuinely famous Java gotcha. The fix is simple: always compare wrapper object values with .equals(), never ==.