Skip to content
Java java5 7 min read

Autoboxing & Unboxing

Java’s type system has two worlds: lean primitive types (int, double, boolean, …) and full-blown objects (Integer, Double, Boolean, …). Autoboxing is the compiler’s magic that converts a primitive into its wrapper object automatically, while unboxing is the reverse — pulling the raw primitive back out. Introduced in Java 5, this feature lets you mix primitives and object APIs without tedious manual wrapping.

The Problem Autoboxing Solves

Before Java 5, adding an int to an ArrayList<Integer> required explicit wrapping:

// Pre-Java 5 (verbose and error-prone)
ArrayList<Integer> list = new ArrayList<>();
list.add(Integer.valueOf(42));  // manual boxing
int x = list.get(0).intValue(); // manual unboxing

With autoboxing, the compiler generates those calls for you:

import java.util.ArrayList;

public class AutoboxingDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(42);        // autoboxing: int → Integer
        int x = list.get(0); // unboxing: Integer → int
        System.out.println("Value: " + x);
    }
}

Output:

Value: 42

Autoboxing: Primitives → Wrapper Objects

Autoboxing happens whenever a primitive is used where an object is expected — in assignments, method calls, or collections.

public class BoxingExamples {
    public static void main(String[] args) {
        // Assignment
        Integer a = 100;       // int  → Integer
        Double  d = 3.14;      // double → Double
        Boolean b = true;      // boolean → Boolean
        Long    l = 999L;      // long → Long

        // Passing to a method that expects Object
        System.out.println(a); // println(Object) — autoboxes
        System.out.println(d);
    }
}

Output:

100
3.14

The compiler silently rewrites Integer a = 100; into Integer a = Integer.valueOf(100);. You can verify this with the javap tool.

Unboxing: Wrapper Objects → Primitives

Unboxing happens whenever a wrapper object is used in a context that needs a primitive — arithmetic, comparisons with ==, or assignment to a primitive variable.

public class UnboxingExamples {
    public static void main(String[] args) {
        Integer x = 50;
        Integer y = 30;

        int sum = x + y;       // unbox both, then add
        boolean bigger = x > y; // unbox both, then compare

        System.out.println("Sum: " + sum);
        System.out.println("x > y: " + bigger);
    }
}

Output:

Sum: 80
x > y: true

The Conversion Table

PrimitiveWrapper ClassAutoboxing methodUnboxing method
byteByteByte.valueOf(b)byteValue()
shortShortShort.valueOf(s)shortValue()
intIntegerInteger.valueOf(i)intValue()
longLongLong.valueOf(l)longValue()
floatFloatFloat.valueOf(f)floatValue()
doubleDoubleDouble.valueOf(d)doubleValue()
charCharacterCharacter.valueOf(c)charValue()
booleanBooleanBoolean.valueOf(b)booleanValue()

See the Wrapper Classes page for the full API each wrapper exposes.

Where Autoboxing Happens Automatically

You don’t need to trigger it manually — the compiler handles these situations:

  • CollectionsList<Integer>, Map<String, Double>, etc. only store objects, so any primitive you add gets autoboxed.
  • Generics — Type parameters are always object types; primitives get boxed to fit.
  • AssignmentInteger i = 7;
  • Method arguments — passing an int where an Integer (or Object) parameter is declared.
  • Return statements — returning an int from a method declared to return Integer.
  • Arithmetic on wrappersInteger a = 10; Integer b = a + 5; unboxes a, adds 5, then re-boxes the result.
import java.util.HashMap;

public class WhereBoxingHappens {
    // Return type is Integer, but we return an int literal
    static Integer square(int n) {
        return n * n; // autoboxes the int result
    }

    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 95);     // 95 (int) → Integer
        scores.put("Bob",   88);

        int aliceScore = scores.get("Alice"); // Integer → int
        System.out.println("Alice: " + aliceScore);
        System.out.println("4² = " + square(4));
    }
}

Output:

Alice: 95
4² = 16

Common Pitfalls

NullPointerException on Unboxing

If a wrapper reference is null and you try to unbox it into a primitive, you get a NullPointerException — not a compile error.

public class NullUnboxing {
    public static void main(String[] args) {
        Integer value = null;
        int x = value; // NullPointerException at runtime!
        System.out.println(x);
    }
}

Warning: Always null-check wrapper references before using them in primitive contexts, especially values returned from maps or optional method results.

== Compares References, Not Values

Two Integer objects compared with == compare object identity, not numeric value. This produces surprising results for values outside the cached range (see the Under the Hood section below).

public class WrapperEquality {
    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        System.out.println(a == b);      // true  (cached)

        Integer c = 128;
        Integer d = 128;
        System.out.println(c == d);      // false (different objects!)
        System.out.println(c.equals(d)); // true  (correct way)
    }
}

Output:

true
false
true

Warning: Always use .equals() to compare wrapper objects. Never rely on == for value equality.

Unintended remove() Overload

With ArrayList<Integer>, calling remove(1) removes the element at index 1, not the value 1. To remove the value, box it explicitly:

import java.util.ArrayList;

public class RemoveAmbiguity {
    public static void main(String[] args) {
        ArrayList<Integer> nums = new ArrayList<>();
        nums.add(10); nums.add(1); nums.add(20);

        nums.remove(1);                  // removes index 1 → removes 1
        System.out.println(nums);        // [10, 20]

        nums.add(10);
        nums.remove(Integer.valueOf(10)); // removes value 10
        System.out.println(nums);        // [20, 10] → [20]
    }
}

Output:

[10, 20]
[20]

Performance in Tight Loops

Boxing and unboxing create short-lived heap objects, which adds GC pressure. In a hot loop summing millions of integers, using Integer instead of int can be measurably slower.

public class BoxingPerf {
    public static void main(String[] args) {
        // Slow: boxes every intermediate result
        Long sumBoxed = 0L;
        for (long i = 0; i < 1_000_000; i++) {
            sumBoxed += i;  // unbox sumBoxed, add i, re-box result
        }

        // Fast: no boxing
        long sumPrimitive = 0L;
        for (long i = 0; i < 1_000_000; i++) {
            sumPrimitive += i;
        }

        System.out.println(sumBoxed.equals(sumPrimitive)); // true, same answer
    }
}

Tip: Use primitive types (int, long, double) for numeric loop variables and accumulators. Switch to wrapper types only when the API demands an object (collections, generics, nullable fields).

Under the Hood

Integer Cache

The JVM caches Integer objects for values in the range -128 to 127 (inclusive). When you call Integer.valueOf(n) — which is exactly what autoboxing calls — values in that range return a pre-existing cached object rather than allocating a new one. Values outside that range always create a new heap object.

This is why Integer a = 127; Integer b = 127; a == b is true (same cached object) but Integer c = 128; Integer d = 128; c == d is false (two distinct objects).

The same caching applies to Byte, Short, Long (same -128..127 range), Character (0..127), and Boolean (always cached — only two possible values).

Note: The upper bound of the Integer cache can be raised with the JVM flag -XX:AutoBoxCacheMax=<n>, but the lower bound (-128) is fixed.

Bytecode View

The compiler transforms autoboxing to valueOf() calls and unboxing to xxxValue() calls in the bytecode. For example:

Integer i = 42;     // compiles to: Integer.valueOf(42)
int x = i;          // compiles to: i.intValue()

You can see this yourself by compiling a class and running javap -c ClassName. The javap tool page walks you through how to read the bytecode output.

JIT Optimization

The JIT compiler can escape-analyze short-lived wrapper objects and sometimes eliminate the heap allocation entirely if the object never “escapes” the current method. So in simple cases the overhead disappears at runtime — but you cannot rely on this for complex loops or objects stored in fields.

Quick Reference

ScenarioBoxing directionWhat the compiler generates
Integer i = 5;autoboxInteger.valueOf(5)
int x = new Integer(5);unboxnew Integer(5).intValue()
list.add(3);autoboxlist.add(Integer.valueOf(3))
int y = list.get(0);unboxlist.get(0).intValue()
Integer a = null; int b = a;unboxNullPointerException at runtime
  • Wrapper Classes — the full API of Integer, Double, Boolean, and friends
  • Generics — why collections require object types and how type parameters interact with boxing
  • ArrayList — the most common place autoboxing silently happens
  • Data Types — Java’s eight primitive types and their ranges
  • Common Mistakes & Pitfalls — more gotchas including the == trap explained above
  • Stream API — streams offer specialized IntStream, LongStream, and DoubleStream to avoid boxing overhead in pipelines
Last updated June 13, 2026
Was this helpful?