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
| Primitive | Wrapper Class | Autoboxing method | Unboxing method |
|---|---|---|---|
byte | Byte | Byte.valueOf(b) | byteValue() |
short | Short | Short.valueOf(s) | shortValue() |
int | Integer | Integer.valueOf(i) | intValue() |
long | Long | Long.valueOf(l) | longValue() |
float | Float | Float.valueOf(f) | floatValue() |
double | Double | Double.valueOf(d) | doubleValue() |
char | Character | Character.valueOf(c) | charValue() |
boolean | Boolean | Boolean.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:
- Collections —
List<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.
- Assignment —
Integer i = 7; - Method arguments — passing an
intwhere anInteger(orObject) parameter is declared. - Return statements — returning an
intfrom a method declared to returnInteger. - Arithmetic on wrappers —
Integer a = 10; Integer b = a + 5;unboxesa, 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
Integercache 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
| Scenario | Boxing direction | What the compiler generates |
|---|---|---|
Integer i = 5; | autobox | Integer.valueOf(5) |
int x = new Integer(5); | unbox | new Integer(5).intValue() |
list.add(3); | autobox | list.add(Integer.valueOf(3)) |
int y = list.get(0); | unbox | list.get(0).intValue() |
Integer a = null; int b = a; | unbox | NullPointerException at runtime |
Related Topics
- 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, andDoubleStreamto avoid boxing overhead in pipelines