Java Autoboxing and Unboxing
Table of Contents
In the last lesson you learned about Java wrapper classes, where int has a wrapper object called Integer. Do you convert between them by hand every time? No. Java usually does it for you, and that automatic swap is called autoboxing and unboxing.
The problem this solves
Java keeps numbers in two worlds, and you constantly cross between them:
- Primitives like
int,double,booleanare fast and simple. - Wrapper objects like
Integer,Double,Booleanare real objects you can store in collections. - A
Listholds only objects, so a list of numbers needsInteger, but your maths uses plainint. - Without help, you would write conversion code everywhere.
Early Java really did make you convert by hand, like this.
Integer boxed = Integer.valueOf(5); // turn int into Integer by handint back = boxed.intValue(); // turn Integer into int by handSystem.out.println(back);Java now does both conversions in the background, so you write the simple version.
Output
5What autoboxing means
Autoboxing is the automatic conversion of a primitive into its wrapper object. The plain value gets wrapped in a little box, which is the object.
Here you assign an int straight into an Integer, with no conversion code.
int number = 42;Integer boxed = number; // β
autoboxing: int becomes Integer on its ownSystem.out.println(boxed);- Java sees an
intgoing into anInteger. - It quietly calls
Integer.valueOf(number)for you. - The result is an
Integerobject holding42.
Output
42You can box a literal too. Java treats it as a primitive first, then wraps it.
Integer count = 100; // β
the literal 100 is boxed into an IntegerDouble price = 19.99; // β
the literal 19.99 is boxed into a DoubleSystem.out.println(count + " and " + price);Output
100 and 19.99What unboxing means
Unboxing is the automatic conversion of a wrapper object back into its primitive. Java does it whenever it needs a primitive but you handed it a wrapper.
Here an Integer flows back into an int.
Integer boxed = 42;int number = boxed; // β
unboxing: Integer becomes int on its ownSystem.out.println(number);- Java sees an
Integergoing into anint. - It quietly calls
boxed.intValue()for you. - The plain
42comes out of the box into yourint.
Output
42Where it happens automatically
You never ask for boxing. Java does it in these everyday spots:
- Plain assignment:
intintoInteger, orIntegerintoint. - Collections: a
List,Map, orSetholds only objects, so primitives get boxed in and unboxed out. - Method calls: a primitive passed where a wrapper is expected gets boxed, and the reverse gets unboxed.
- Arithmetic on wrappers: Java unboxes them, does the maths, then boxes the result.
Collections are the big one. You add plain int values and read one back into a plain int.
import java.util.ArrayList;import java.util.List;
public class Main { public static void main(String[] args) { List<Integer> scores = new ArrayList<>(); scores.add(90); // β
autoboxing: int 90 becomes Integer scores.add(85); int first = scores.get(0); // β
unboxing: Integer becomes int System.out.println("First score: " + first); }}The list only ever held Integer objects, yet you wrote no conversion in either direction.
Output
First score: 90In method calls, a method that wants an Integer accepts an int because Java boxes it on the way in.
public class Main { static void show(Integer value) { // method wants an Integer System.out.println("Got: " + value); }
public static void main(String[] args) { int n = 7; show(n); // β
autoboxing: int n is boxed to match the parameter }}Output
Got: 7In arithmetic, Java cannot add two objects directly, so it unboxes both, adds, then boxes the result.
Integer a = 10;Integer b = 3;Integer sum = a + b; // β
a and b are unboxed, added, then sum is boxedSystem.out.println(sum);Output
13The NullPointerException trap
A wrapper can be null, but a primitive cannot. So unboxing a null crashes:
- There is no number in the box to pull out.
- Java tries to call
intValue()on nothing. - The program stops with a
NullPointerException.
Integer boxed = null;int number = boxed; // β unboxing null: nothing to take out, this crashesSystem.out.println(number);Output
Exception in thread "main" java.lang.NullPointerException at Main.main(Main.java:3)This hides in a Map lookup that returns null for a missing key.
import java.util.HashMap;import java.util.Map;
public class Main { public static void main(String[] args) { Map<String, Integer> ages = new HashMap<>(); ages.put("Alex", 30); int age = ages.get("Sam"); // β "Sam" is missing, get returns null, then unboxing crashes System.out.println(age); }}"Sam" is not in the map, so get returns null, and unboxing that into int age crashes. The fix is to keep it as an Integer, check for null, or use a default.
Null and primitives do not mix
A primitive can never be null. So any time you unbox a wrapper that might be null, you risk a crash. Check for null before you unbox, especially with values that came from a map, a database, or a method that can return nothing.
The cost of boxing in tight loops
Boxing is handy but not free:
- Each box creates a new object.
- Millions of tiny objects in a loop is slow and wastes memory.
- You rarely notice, except in a loop that runs many times.
Watch this mistake. The total is Long (wrapper) instead of long (primitive).
public class Main { public static void main(String[] args) { Long sum = 0L; // β wrapper type: every step boxes a new Long for (long i = 0; i < 100_000_000L; i++) { sum += i; // unbox sum, add, box the result back, every loop } System.out.println(sum); }}Each sum += i unboxes, adds, then boxes a brand new Long. Across a hundred million loops that is a hundred million throwaway objects, which is slow and hammers the garbage collector.
Output
4999999950000000The fix is a primitive long. Same answer, no boxing.
public class Main { public static void main(String[] args) { long sum = 0L; // β
primitive type: no objects created at all for (long i = 0; i < 100_000_000L; i++) { sum += i; // plain addition, fast, no boxing } System.out.println(sum); }}Every step is plain primitive maths, so no objects are created and it runs far faster. For a value you change many times in a loop, use the primitive.
Output
4999999950000000The Integer cache and == again
Comparing wrappers with == is risky, and autoboxing makes the trap easy to hit:
- Java caches
Integerobjects for values-128to127. - Two boxed values in that range can be the very same object.
- Outside that range, Java makes a fresh object each time.
Integer a = 100;Integer b = 100;System.out.println(a == b); // β true here, but only because of the cache
Integer c = 200;Integer d = 200;System.out.println(c == d); // β false: two separate objects100 is cached, so a and b are the same object and == is true. 200 is outside the range, so c and d differ and == is false. Same code shape, opposite result.
Output
truefalse== checks if two references point at the same object. To compare the values inside, use .equals.
Integer c = 200;Integer d = 200;System.out.println(c.equals(d)); // β
true: compares the values.equals looks at the numbers, not at which objects they are.
Output
trueCommon Mistakes
The boxing errors people hit most. Watch for these traps.
Unboxing a value that might be null. A null wrapper cannot become a primitive, so check first.
// β Avoid: if the value is null, this crashesint age = ages.get("Sam");
// β
Good: keep it boxed and check, or use a defaultInteger found = ages.get("Sam");int age = (found != null) ? found : 0;Using == to compare wrappers. It compares objects, not values, and the cache makes it lie for small numbers.
// β Avoid: true or false depending on the cache, never reliableif (a == b) { }
// β
Good: compare the valuesif (a.equals(b)) { }Boxing inside a hot loop. A wrapper accumulator creates a new object every step, so use the primitive.
// β Avoid: every += boxes a new LongLong sum = 0L;for (long i = 0; i < 1_000_000L; i++) sum += i;
// β
Good: plain primitive, no objectslong sum = 0L;for (long i = 0; i < 1_000_000L; i++) sum += i;Best Practices
Habits that keep boxing safe and fast.
- Prefer primitives by default. Use
int,long,doublefor plain values and loop counters. Reach forIntegeronly when you need an object, like inside a collection. - Check for null before unboxing. Any wrapper from a map, a database, or a method that can return nothing might be
null. Guard it first. - Never use == on wrappers. Always use
.equals, since the cache makes==give different answers for different numbers. - Keep accumulators primitive. A running total or counter inside a loop should be a primitive, so no objects are created each step.
- Let autoboxing help, but know it is there. It keeps collection code clean. Just remember a hidden box or unbox is happening, so the null and performance traps stay on your radar.
What Youβve Learned
Good work. Letβs recap autoboxing and unboxing.
- β
Autoboxing is the automatic conversion of a primitive into its wrapper, like
intintoInteger. - β
Unboxing is the reverse, a wrapper back into a primitive, like
Integerintoint. - β
It happens automatically in assignment, in collections like
List<Integer>, in method arguments, and in arithmetic on wrappers. - β
Unboxing a
nullwrapper throws aNullPointerException, because a primitive cannot benull. - β Boxing in a tight loop creates many throwaway objects and is slow, so use primitives for accumulators.
- β
The
Integercache (-128to127) makes==unreliable on wrappers, so compare with.equals.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What is autoboxing?
Why: Autoboxing is Java automatically wrapping a primitive like int into its wrapper object like Integer.
- 2
What happens when Java unboxes a null Integer into an int?
Why: A primitive cannot be null, so unboxing a null wrapper throws a NullPointerException.
- 3
Why is using a Long accumulator in a tight loop slow?
Why: Each += on a wrapper unboxes, adds, and boxes a new object every loop, which is slow. Use the primitive long.
- 4
Why can Integer a = 100; Integer b = 100; give a == b as true but the same with 200 give false?
Why: Values in the Integer cache range (-128 to 127) reuse the same object, so == is true. Outside it, separate objects make == false. Use .equals.
Whatβs Next?
You now understand how Java moves values between primitives and wrappers on its own. Next, letβs do maths with these values: the operators that add, subtract, multiply, and divide, and the rules behind them.