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

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

Share & Connect