Autoboxing and unboxing

In the last lesson you wrote Integer.valueOf(85) and m.intValue() by hand. Since Java 5 you rarely need to: the compiler does it for you. This is autoboxing (primitive → wrapper) and unboxing (wrapper → primitive). It makes code shorter, but it hides method calls, and hidden calls can throw NullPointerException or create millions of objects. You'll learn exactly what javac writes and where the traps are.

Module 10 · Core Classes⏱ 18 minIntermediate8 practice problems
1 The story

The line that crashed with no object in sight

Riya's canteen app keeps stock in a HashMap<String, Integer>. Adding items works beautifully: stock.put("samosa", 40). Then a customer orders a “vada pav”, which was never added, and the app crashes on a line that has no dot and no object.

stock.put("samosa", 40); int left = stock.get(item) - 1; // item = "vada pav" NullPointerException Cannot invoke "Integer.intValue()" What javac actually wrote stock.get(item)→ null .intValue()← hidden call - 1 You never typed intValue(). myphatshala.in
Riya

Sir, a NullPointerException needs something like obj.method() on null. My line is just stock.get(item) - 1!

Arjun Sir

The method call is there; you just can't see it. get returns an Integer. To subtract 1, Java needs an int, so the compiler quietly writes .intValue() for you. That's unboxing.

Riya

And “vada pav” isn't in the map, so get returned null…

Arjun Sir

…and the hidden null.intValue() blew up. The same trick runs the other way too: when you wrote put("samosa", 40), javac turned 40 into Integer.valueOf(40). That's autoboxing. Today we'll make the invisible calls visible.

2 Definition

What are autoboxing and unboxing?

Formal definition (write this in exams)

Autoboxing is the automatic conversion, done by the compiler, of a primitive value into an object of its corresponding wrapper class (for example int → Integer), when an object is expected. Unboxing is the reverse: the automatic conversion of a wrapper object into its primitive value when a primitive is expected. Introduced in Java 5, the compiler implements them with calls such as Integer.valueOf(int) and Integer.intValue(). Unboxing a null reference throws NullPointerException.

In simple words

You write Integer x = 5; and int y = x;, and the compiler adds the conversion code for you. Convenient, but the conversion is still a real method call.

Words you'll meet in this lesson

AutoboxingCompiler wraps a primitive: Integer x = 5; becomes Integer.valueOf(5).
UnboxingCompiler unwraps: int y = x; becomes x.intValue().
Syntactic sugarA shortcut in the language that the compiler expands into longer code.
javap -cA JDK tool that prints the bytecode inside a .class file, so you can see the hidden calls.
Overload resolutionHow javac picks between methods with the same name (Lesson 5.4). Boxing is its second choice.

Where it happens

AssigningInteger a = 10; (boxing) · int b = a; (unboxing)
Passing argumentslist.add(7) boxes 7 because add takes an Integer
ReturningA method returning Integer may return 0;
Arithmetic and comparisona + 1, a > 5, a++ unbox first (and a++ boxes the result again)
ConditionsBoolean ok = true; if (ok) … unboxes with booleanValue()

Exam tip: autoboxing only goes to the matching wrapper: int → Integer, never int → Long. Long l = 5; is a compile error (int cannot be converted to Long); write Long l = 5L;.

3 Types and ways to use it

Boxing, unboxing, and what javac writes

Autoboxing

Primitive in, object out. Integer x = 5; → Integer.valueOf(5).

int → Integer

Unboxing

Object in, primitive out. int y = x; → x.intValue().

Integer → int

Both in one line

x++ or x += 1 on an Integer: unbox, add, box a new object.

read-modify-write

The same code, written two ways

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> marks = new ArrayList<>();

        // What you write (Java 5+)
        marks.add(85);
        int first = marks.get(0);

        // What javac writes for you
        marks.add(Integer.valueOf(92));
        int second = marks.get(1).intValue();

        System.out.println(marks + " " + (first + second));
    }
}
[85, 92] 177

Proof: the bytecode

Compile this tiny class and run javap -c B to see the instructions inside B.class:

public class B {
    public static void main(String[] args) {
        Integer count = 5;
        int n = count;
        count++;
    }
}
 0: iconst_5
 1: invokestatic  #7   // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;   ← Integer count = 5
 4: astore_1
 5: aload_1
 6: invokevirtual #13  // Method java/lang/Integer.intValue:()I                    ← int n = count
 9: istore_2
10: aload_1
11: astore_3
12: aload_1
13: invokevirtual #13  // Method java/lang/Integer.intValue:()I                    ← count++ : unbox
16: iconst_1
17: iadd                                                                             ←           add 1
18: invokestatic  #7   // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;   ←           box again
21: astore_1
22: aload_3
23: pop
24: return

There is no special “autobox” instruction. It's ordinary method calls that the compiler inserted, and because it uses valueOf, the Integer cache from Lesson 10.1 still applies.

Trap 1: unboxing null

import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> stock = new HashMap<>();
        stock.put("samosa", 40);

        Integer v = stock.get("vada pav");        // null: not in the map
        System.out.println(v);                    // printing null is fine
        int safe = (v == null) ? 0 : v;           // check before unboxing
        System.out.println(safe);
        System.out.println(stock.getOrDefault("vada pav", 0));
        // int boom = stock.get("vada pav");      // NullPointerException
    }
}
null 0 0
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.HashMap.get(Object)" is null

Trap 2: == between two wrappers

public class Main {
    public static void main(String[] args) {
        Integer a = 1000, b = 1000;   // autoboxing uses valueOf: two objects
        int p = 1000;

        System.out.println(a == b);   // compares references
        System.out.println(a == p);   // one side is int, so a is unboxed
        System.out.println(a.equals(b));
    }
}
false true true

Trap 3: remove(1) on a list of Integers

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> ids = new ArrayList<>(List.of(10, 20, 30));

        ids.remove(1);                    // remove(int index): removes 20
        System.out.println(ids);

        ids.remove(Integer.valueOf(10));  // remove(Object): removes the value 10
        System.out.println(ids);
    }
}
[10, 30] [30]

javac prefers a method that needs no boxing, so remove(1) picks remove(int index). Overload resolution tries, in order: exact match or widening, then boxing/unboxing, then varargs.

Rule of thumb: let autoboxing make collection code readable, but keep calculations in primitives, and treat every unboxing of a value that might be null (map lookups, database fields, method results) as a place to check for null first.

4 Step by step

How javac handles total += price with wrappers

  1. javac sees the types

    Integer total = 0;
    Integer price = 250;
    total += price;
    Both are Integer. The + operator works only on primitives (or Strings), so javac plans conversions.
  2. Box the literals

    Integer total = 0; becomes Integer.valueOf(0), and 250 becomes Integer.valueOf(250). 0 comes from the cache; 250 is a new object.
  3. Unbox both operands

    total.intValue() and price.intValue() copy 0 and 250 onto the operand stack. If either variable were null, this is where the NullPointerException would happen.
  4. Add as ints

    An ordinary iadd instruction: 0 + 250 = 250, a primitive result.
  5. Box the result into a new object

    Integer.valueOf(250) creates another Integer object (250 is outside the cache), and total now points to it. The old object holding 0 is untouched: wrappers are immutable. In a loop that runs a million times, that's up to a million new objects.
5 See it happen

Boxing in, unboxing out: both views

Left: the code you write. Right: the calls javac inserted, and what they do to the stack and heap.

Autoboxing and unboxing, made visible 6 steps
What you see Integer total = 0; Integer price = 250; total += price; int n = total; Integer none = null; int bad = none; Console println(total) → 250 NullPointerException Looks like storing a number. Looks like storing a number. Looks like simple addition. Looks like a plain copy. A wrapper variable can be null. A line with no dot crashes! myphatshala.in
Behind the screen javac writes: Integer.valueOf(0) Integer.valueOf(250) valueOf(total.intValue() + price.intValue()) total.intValue() (nothing to call: just null) none.intValue() ← on null! Stack Heap total → price → n 250 none null Integer 0cached Integer 250#1 price Integer 250#2 new result intValue() on null ✗ 0 is in the cache: no new object. 250 is outside the cache: new object #1. Unbox both, add as ints, box the result: object #2. Unboxing copies 250 into a plain int. none points nowhere. The hidden intValue() has no object to run on. myphatshala.in
6 Real-world use

Autoboxing in real systems

Every time Java code puts numbers into collections, maps, streams or generic APIs, autoboxing is at work. Most of the time that's fine; a few places need care.

Watch it with an example: an exam result portal

Marks come from a database where an absent student's mark is NULL. Watch the averaging code survive one absent student, then see the version that crashes.

Averaging marks with an absent student 5 steps
Animated example 78 Riya 91 Kabir null Meera 64 Arjun List<Integer> for (Integer m : marks) if (m != null) { sum += m; count++; } sum += 78 (unbox 78) null: skipped, not counted sum = 233, count = 3 average = 233 / 3 = 77.67 for (int m : marks) // unboxes every element sum += m; NullPointerException at Meera Meera was absent: her mark is null, not 0. A real mark is unboxed and added. The null is skipped before any unboxing. Average of the 3 students who wrote the exam. Declaring the loop variable as int unboxes null. myphatshala.in
"samosa" → 40"chai" → 75

1. Counting with maps

Inventory, word counts, votes and page views are kept in Map<String, Integer>. Code like map.put(k, map.getOrDefault(k, 0) + 1) unboxes, adds and boxes on every update.

getOrDefault→unbox + 1 + box→put back
discountNULL

2. Nullable database columns

Entity classes map optional columns to Integer or Double fields. A business rule written as int d = order.getDiscount(); crashes on the first order without a discount.

NULL column→Integer field = null→check before unboxing
millions of points

3. High-volume analytics

Summing sensor readings or transaction amounts in a Long or a List<Double> creates an object per value. Performance-sensitive code uses long, arrays, or primitive streams like IntStream (Lesson 14.3).

Hot loop→primitives, not wrappers→less garbage
idid==

4. Comparing IDs

Code that compares Integer or Long IDs with == passes tests with small IDs and fails in production with large ones. Teams use Objects.equals and code-review rules to catch it.

Long id1, id2→Objects.equals(id1, id2)→safe comparison

Examples are simplified. Frameworks and libraries add their own conversion layers on top.

7 Run the code

A canteen stock counter

import java.util.HashMap;

public class Main {
    static HashMap<String, Integer> stock = new HashMap<>();

    static String sell(String item, int qty) {
        Integer have = stock.get(item);           // may be null
        if (have == null) {
            return item + ": not on the menu";
        }
        if (have < qty) {                         // unboxing for <
            return item + ": only " + have + " left";
        }
        stock.put(item, have - qty);              // unbox, subtract, box
        return item + ": sold " + qty + ", left " + stock.get(item);
    }

    public static void main(String[] args) {
        stock.put("samosa", 40);                  // 40 is autoboxed
        stock.put("chai", 75);
        System.out.println(sell("samosa", 5));
        System.out.println(sell("chai", 80));
        System.out.println(sell("vada pav", 2));
    }
}
samosa: sold 5, left 35 chai: only 75 left vada pav: not on the menu
  • stock.put("samosa", 40)Autoboxing: javac writes Integer.valueOf(40).
  • Integer have = stock.get(item)Kept as Integer on purpose, so it can hold null.
  • have < qtyUnboxing: have.intValue() < qty. Safe, because null was ruled out above.
  • have - qtyUnbox, subtract as ints, box the result for put.
8 Line by line

Every += makes a new object

This trace is recorded from the real JVM. Watch total point to a different Integer object after each addition, while the old ones stay unchanged.

Worked examples: real problems, solved and traced in memory

Three tasks where autoboxing helps, and where one careless unboxing would crash the program.

Example 1Elections · counting

Count votes in a map

Problem statement

A class election records each vote as a name. Count the votes per candidate in a HashMap<String, Integer> without crashing on a candidate's first vote.

Input
"Riya", "Kabir", "Riya"
Expected output
{Riya=2, Kabir=1}

Approach

  1. A helper vote(map, name) reads the current count with getOrDefault(name, 0), so a new name gives 0, not null.
  2. Store now + 1: unboxing for the addition, boxing for put.
  3. Print the map.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2EdTech · reports

Class average with absent students

Problem statement

Marks are in a List<Integer>; null means absent. Compute the average of the students who were present.

Input
[78, 91, null, 64]
Expected output
Average: 77.67

Approach

  1. A helper average(List<Integer> marks) loops with an Integer loop variable, so nothing is unboxed automatically.
  2. Skip nulls; unbox the rest by adding them to an int sum.
  3. Return the average as a double and print it with two decimals.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3E-commerce · bug fix

Remove a product ID, not a position

Problem statement

A wishlist stores product IDs [7, 2, 9]. The customer removes product 2. The code ids.remove(productId) with an int removed the wrong item. Fix it.

Input
ids = [7, 2, 9], remove product 2
Expected output
By index: [7, 2]
By value: [7, 9]

Approach

  1. remove(int) removes by index; remove(Object) removes by value.
  2. Passing an int makes javac choose remove(int) (no boxing needed).
  3. Box the ID explicitly with Integer.valueOf to call remove(Object).

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 10.2 in one screen

  • Autoboxing: primitive → wrapper, compiled to Integer.valueOf(…). Unboxing: wrapper → primitive, compiled to intValue() etc.
  • Happens in assignments, arguments, returns, arithmetic, comparisons and conditions. Since Java 5.
  • Only to the matching wrapper: Long l = 5; ✗, Long l = 5L; ✓.
  • Unboxing null → NullPointerException. Watch map lookups and nullable fields; use getOrDefault or a null check.
  • wrapper == wrapper compares references; wrapper == primitive unboxes. Use equals.
  • Overloads: exact/widening first, then boxing, then varargs. list.remove(1) removes index 1.
  • x++ on an Integer creates a new object. Use primitives in loops and sums.

Interview and viva questions

What is autoboxing and when was it introduced?

Automatic conversion of a primitive to its wrapper (and unboxing for the reverse), done by the compiler, introduced in Java 5 together with generics. The compiler inserts calls like Integer.valueOf and intValue(); the JVM has no special instruction for it.

Can autoboxing cause a NullPointerException?

Unboxing can. If a wrapper reference is null and it's used where a primitive is needed (int n = map.get(k);, total + v, if (flag) with a null Boolean), the hidden intValue()/booleanValue() call throws. Boxing itself never throws.

Why does Integer a = 127, b = 127; a == b give true but 128 gives false?

Autoboxing uses Integer.valueOf, which returns cached objects for -128 to 127. 128 is boxed into two separate objects, and == compares references.

How does boxing affect method overloading?

javac resolves overloads in three phases: (1) without boxing or varargs, allowing widening, (2) with boxing/unboxing, (3) with varargs. So f(5) prefers f(long) over f(Integer), and list.remove(1) picks remove(int).

Is autoboxing bad for performance?

Not in normal code. In hot loops or large data it creates many short-lived objects and adds unboxing work. Use primitives, primitive arrays, or IntStream/LongStream there, and measure before optimising.

Practice problems

Find the hidden calls, prevent null crashes and pick the right overload. The last three are business tasks.

P1Easy

Box and unbox without calling methods

Store 45 in an Integer, add 5 to it using an int, and print both, writing no valueOf or intValue yourself.

Input
45
Expected output
45 50
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        Integer boxed = 45;
        int plain = boxed + 5;
        System.out.println(boxed + " " + plain);
    }
}
45 50

How it works: line 3 is autoboxed to Integer.valueOf(45); in line 4 boxed is unboxed with intValue() before adding.

P2Easy

Safe lookup with getOrDefault

A price map has only "tea" → 15. Print the price of "coffee" as 0 without crashing.

Input
"coffee"
Expected output
coffee: 0
Show solution and explanation
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> price = new HashMap<>();
        price.put("tea", 15);
        int coffee = price.getOrDefault("coffee", 0);
        System.out.println("coffee: " + coffee);
    }
}
coffee: 0

How it works: get would return null and unboxing it into int would throw. getOrDefault returns 0 (boxed) instead.

P3Easy

Fix the Long literal

Long views = 1200; doesn't compile. Fix it and print views doubled.

Input
1200
Expected output
2400
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        Long views = 1200L;
        System.out.println(views * 2);
    }
}
2400

How it works: 1200 is an int, which can only be boxed to Integer. The L suffix makes it a long, which boxes to Long.

P4Medium

Compare two boxed scores correctly

Two players scored 500 each, stored as Integer. Print whether they tied, correctly.

Input
500, 500
Expected output
== says     : false
equals says : true
Show solution and explanation
import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        Integer p1 = 500, p2 = 500;
        System.out.println("== says     : " + (p1 == p2));
        System.out.println("equals says : " + Objects.equals(p1, p2));
    }
}
== says : false equals says : true

How it works: autoboxing 500 creates two objects, so == is false. Objects.equals compares values and is also safe if either is null.

P5Medium

Which overload runs?

Predict, then confirm, which method each call picks: show(int) doesn't exist, but show(long), show(Integer) and show(Object) do.

Input
show(5), show(Integer.valueOf(5)), show("x")
Expected output
long
Integer
Object
Show solution and explanation
public class Main {
    static void show(long x)    { System.out.println("long"); }
    static void show(Integer x) { System.out.println("Integer"); }
    static void show(Object x)  { System.out.println("Object"); }

    public static void main(String[] args) {
        show(5);
        show(Integer.valueOf(5));
        show("x");
    }
}
long Integer Object

How it works: for show(5), javac first looks for a match without boxing: widening int → long works, so show(long) wins. An Integer argument matches show(Integer) exactly. A String only fits Object.

P6Business problem

Shopping cart quantities

Add items to a cart map: pen, pen, book, pen. Use merge so first-time items start at 1, then print the cart.

Input
pen, pen, book, pen
Expected output
{pen=3, book=1}
Show solution and explanation
import java.util.LinkedHashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> cart = new LinkedHashMap<>();
        String[] added = {"pen", "pen", "book", "pen"};
        for (String item : added) {
            cart.merge(item, 1, Integer::sum);
        }
        System.out.println(cart);
    }
}
{pen=3, book=1}

How it works: merge puts 1 for a new key, otherwise combines the old and new values with Integer::sum (a method reference, Lesson 14.1). No null is ever unboxed. (LinkedHashMap keeps insertion order; Lesson 11.6.)

P7Business problem

Optional discount from a nullable field

Orders have an Integer discountPct that may be null. Compute the final price for ₹1000 with 10% and with no discount.

Input
1000 with 10, 1000 with null
Expected output
900
1000
Show solution and explanation
public class Main {
    static int finalPrice(int price, Integer discountPct) {
        int pct = (discountPct == null) ? 0 : discountPct;
        return price - price * pct / 100;
    }

    public static void main(String[] args) {
        System.out.println(finalPrice(1000, 10));
        System.out.println(finalPrice(1000, null));
    }
}
900 1000

How it works: the null check happens before unboxing, inside one helper, so no caller can forget it. The literal 10 is autoboxed when passed.

P8Business problem

Fast sum of daily sales

Sum one million sales of ₹1 each, first with a Long total, then with a long. Print both totals (the timing is up to you to measure).

Input
1,000,000 × ₹1
Expected output
1000000 1000000
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        Long boxedTotal = 0L;
        long plainTotal = 0L;
        for (int i = 0; i < 1_000_000; i++) {
            boxedTotal += 1;   // unbox, add, box a new Long
            plainTotal += 1;   // just add
        }
        System.out.println(boxedTotal + " " + plainTotal);
    }
}
1000000 1000000

How it works: both totals are correct, but the Long version creates a new object on almost every iteration. Wrap each loop with System.nanoTime() to see the difference on your machine.

Real-world scenario questions (asked in company interviews)

Autoboxing bugs are favourites in interviews because the code looks correct. Read the line, then ask: where is the hidden call?

Retail · production bugFresher

Crash on a new product

The billing service crashes with NullPointerException at int qty = cart.get(sku) + 1; only when a customer adds a product for the first time. There's no method call on an object in that line. Explain.

The interviewer is checking: can you see the hidden unboxing?

Show a strong answer
  • cart.get(sku) returns Integer. For a new SKU it returns null.
  • + 1 needs an int, so javac inserted .intValue(): null.intValue() throws.
  • Fix: cart.getOrDefault(sku, 0) + 1, or better, cart.merge(sku, 1, Integer::sum).
  • Add a unit test for the first-time-add path.

Avoid: wrapping it in try/catch (NullPointerException). Fix the cause, not the symptom.

Fintech · code reviewJunior

Duplicate payments slip through

A duplicate check does if (newTxn.getAmountPaise() == lastTxn.getAmountPaise()), both returning Long. Small test amounts were caught; real ₹500 payments (50000 paise) are not. Why, and what's the fix?

The interviewer is checking: wrapper equality and the Long cache.

Show a strong answer
  • Long also caches only -128 to 127. 50000 creates distinct objects, so == is false.
  • Use Objects.equals(a, b), or long return types if the amount can never be null.
  • Prefer primitives for amounts in domain logic; it removes both the == trap and null unboxing.

Avoid: “just increase the cache size”. That hides the bug for some values only.

Analytics · performanceJunior

GC pauses in a reporting job

A nightly job sums 50 million values from a List<Double> into a Double total. GC logs show huge allocation rates. What would you change?

The interviewer is checking: awareness of boxing cost at scale.

Show a strong answer
  • Double total boxes a new object on every +=; the list itself holds 50 million Double objects.
  • Use double total, and if possible a double[] or DoubleStream instead of List<Double>.
  • Measure allocation before and after (profiler or GC logs) to prove the improvement.

Avoid: tuning GC flags first. Remove the needless objects, then tune.

Platform · API designMid

Should this method return int or Integer?

You're designing findDiscountPercent(String couponCode). Some codes are invalid. A teammate wants it to return Integer and use null for invalid. What do you think?

The interviewer is checking: null design and unboxing risk for callers.

Show a strong answer
  • Returning Integer with null invites callers to write int d = findDiscountPercent(c);, which crashes for invalid codes.
  • Better: return OptionalInt (or Optional<Integer>, Lesson 14.4), which forces callers to handle “absent”, or throw a clear exception for invalid codes.
  • If null is kept, document it and name the method to signal it, e.g. discountOrNull.

Avoid: returning 0 for invalid codes. A valid “0% discount” and an invalid code become indistinguishable.