Java Autoboxing and Unboxing

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, boolean are fast and simple.
  • Wrapper objects like Integer, Double, Boolean are real objects you can store in collections.
  • A List holds only objects, so a list of numbers needs Integer, but your maths uses plain int.
  • 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 hand
int back = boxed.intValue(); // turn Integer into int by hand
System.out.println(back);

Java now does both conversions in the background, so you write the simple version.

Output

5

What 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 own
System.out.println(boxed);
  • Java sees an int going into an Integer.
  • It quietly calls Integer.valueOf(number) for you.
  • The result is an Integer object holding 42.

Output

42

You 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 Integer
Double price = 19.99; // βœ… the literal 19.99 is boxed into a Double
System.out.println(count + " and " + price);

Output

100 and 19.99

What 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 own
System.out.println(number);
  • Java sees an Integer going into an int.
  • It quietly calls boxed.intValue() for you.
  • The plain 42 comes out of the box into your int.

Output

42

Where it happens automatically

You never ask for boxing. Java does it in these everyday spots:

  • Plain assignment: int into Integer, or Integer into int.
  • Collections: a List, Map, or Set holds 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: 90

In 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: 7

In 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 boxed
System.out.println(sum);

Output

13

The 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 crashes
System.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

4999999950000000

The 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

4999999950000000

The Integer cache and == again

Comparing wrappers with == is risky, and autoboxing makes the trap easy to hit:

  • Java caches Integer objects for values -128 to 127.
  • 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 objects

100 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

true
false

== 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

true

Common 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 crashes
int age = ages.get("Sam");
// βœ… Good: keep it boxed and check, or use a default
Integer 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 reliable
if (a == b) { }
// βœ… Good: compare the values
if (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 Long
Long sum = 0L;
for (long i = 0; i < 1_000_000L; i++) sum += i;
// βœ… Good: plain primitive, no objects
long 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, double for plain values and loop counters. Reach for Integer only 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 int into Integer.
  • βœ… Unboxing is the reverse, a wrapper back into a primitive, like Integer into int.
  • βœ… It happens automatically in assignment, in collections like List<Integer>, in method arguments, and in arithmetic on wrappers.
  • βœ… Unboxing a null wrapper throws a NullPointerException, because a primitive cannot be null.
  • βœ… Boxing in a tight loop creates many throwaway objects and is slow, so use primitives for accumulators.
  • βœ… The Integer cache (-128 to 127) makes == unreliable on wrappers, so compare with .equals.

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is autoboxing?

    Why: Autoboxing is Java automatically wrapping a primitive like int into its wrapper object like Integer.

  2. 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. 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. 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.

Java Arithmetic Operators