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.
Sir, a NullPointerException needs something like obj.method() on null. My line is just stock.get(item) - 1!
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.
And “vada pav” isn't in the map, so get returned null…
…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.
What are autoboxing and unboxing?
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.
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
Integer x = 5; becomes Integer.valueOf(5).int y = x; becomes x.intValue().Where it happens
| Assigning | Integer a = 10; (boxing) · int b = a; (unboxing) |
|---|---|
| Passing arguments | list.add(7) boxes 7 because add takes an Integer |
| Returning | A method returning Integer may return 0; |
| Arithmetic and comparison | a + 1, a > 5, a++ unbox first (and a++ boxes the result again) |
| Conditions | Boolean 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;.
Boxing, unboxing, and what javac writes
Autoboxing
Primitive in, object out. Integer x = 5; → Integer.valueOf(5).
Unboxing
Object in, primitive out. int y = x; → x.intValue().
Both in one line
x++ or x += 1 on an Integer: unbox, add, box a new object.
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));
}
}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: returnThere 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
}
}Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.HashMap.get(Object)" is nullTrap 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));
}
}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);
}
}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.
How javac handles total += price with wrappers
javac sees the types
Both areInteger total = 0; Integer price = 250; total += price;Integer. The+operator works only on primitives (or Strings), so javac plans conversions.Box the literals
Integer total = 0;becomesInteger.valueOf(0), and 250 becomesInteger.valueOf(250). 0 comes from the cache; 250 is a new object.Unbox both operands
total.intValue()andprice.intValue()copy 0 and 250 onto the operand stack. If either variable were null, this is where theNullPointerExceptionwould happen.Add as ints
An ordinaryiaddinstruction: 0 + 250 = 250, a primitive result.Box the result into a new object
Integer.valueOf(250)creates another Integer object (250 is outside the cache), andtotalnow 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.
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.
- You assign the number 0 to an
Integervariable. No method call in sight. - The same for 250.
total += pricereads like adding two numbers.- Copying an Integer into an int looks like an ordinary assignment.
noneholds no object at all.- This innocent-looking line throws
NullPointerException. The right side shows the hidden call.
- Autoboxing: javac replaces
0withInteger.valueOf(0), which returns the cached object for 0. Integer.valueOf(250)creates a new object, because 250 is outside -128..127.+=needs ints: javac unboxes both withintValue(), adds 0 + 250 as ints, then boxes 250 into a new object.totalmoves to it; the cached 0 is untouched.- Unboxing:
total.intValue()copies the value 250 into the primitiven, stored right in the frame. - Assigning null needs no conversion:
nonesimply holds no reference. int bad = none;becomesnone.intValue(). Calling a method on null throwsNullPointerException.
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.
- Marks arrive as
Integerobjects. Meera was absent, so the database gaveNULL, which becomesnullin Java. - For Riya's 78, the check passes and
sum += munboxes the Integer to add it. - For Meera,
m != nullis false, so nothing is unboxed and she isn't counted. - Average = 233 / 3 ≈ 77.67. Using 0 for Meera would have wrongly pulled it down to about 58.
- The tempting shortcut
for (int m : marks)unboxes each element automatically, and crashes on Meera's null. (Simplified: real portals also record why a mark is missing.)
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.
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.
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).
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.
Examples are simplified. Frameworks and libraries add their own conversion layers on top.
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));
}
}stock.put("samosa", 40)Autoboxing: javac writesInteger.valueOf(40).Integer have = stock.get(item)Kept asIntegeron 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 forput.
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.
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.
"Riya", "Kabir", "Riya"
{Riya=2, Kabir=1}Approach
- A helper
vote(map, name)reads the current count withgetOrDefault(name, 0), so a new name gives 0, not null. - Store
now + 1: unboxing for the addition, boxing forput. - 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.
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.
[78, 91, null, 64]
Average: 77.67
Approach
- A helper
average(List<Integer> marks)loops with anIntegerloop variable, so nothing is unboxed automatically. - Skip nulls; unbox the rest by adding them to an int sum.
- 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.
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.
ids = [7, 2, 9], remove product 2
By index: [7, 2] By value: [7, 9]
Approach
remove(int)removes by index;remove(Object)removes by value.- Passing an
intmakes javac chooseremove(int)(no boxing needed). - Box the ID explicitly with
Integer.valueOfto callremove(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.
Check yourself
Quick revision
Lesson 10.2 in one screen
- Autoboxing: primitive → wrapper, compiled to
Integer.valueOf(…). Unboxing: wrapper → primitive, compiled tointValue()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; usegetOrDefaultor a null check. wrapper == wrappercompares references;wrapper == primitiveunboxes. Useequals.- 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.
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.
45
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);
}
}How it works: line 3 is autoboxed to Integer.valueOf(45); in line 4 boxed is unboxed with intValue() before adding.
Safe lookup with getOrDefault
A price map has only "tea" → 15. Print the price of "coffee" as 0 without crashing.
"coffee"
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);
}
}How it works: get would return null and unboxing it into int would throw. getOrDefault returns 0 (boxed) instead.
Fix the Long literal
Long views = 1200; doesn't compile. Fix it and print views doubled.
1200
2400
Show solution and explanation
public class Main {
public static void main(String[] args) {
Long views = 1200L;
System.out.println(views * 2);
}
}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.
Compare two boxed scores correctly
Two players scored 500 each, stored as Integer. Print whether they tied, correctly.
500, 500
== 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));
}
}How it works: autoboxing 500 creates two objects, so == is false. Objects.equals compares values and is also safe if either is null.
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.
show(5), show(Integer.valueOf(5)), show("x")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");
}
}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.
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.
pen, pen, book, pen
{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);
}
}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.)
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.
1000 with 10, 1000 with null
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));
}
}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.
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).
1,000,000 × ₹1
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);
}
}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?
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)returnsInteger. For a new SKU it returns null.+ 1needs 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.
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
Longalso caches only -128 to 127. 50000 creates distinct objects, so==is false.- Use
Objects.equals(a, b), orlongreturn 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.
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 totalboxes a new object on every+=; the list itself holds 50 million Double objects.- Use
double total, and if possible adouble[]orDoubleStreaminstead ofList<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.
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
Integerwith null invites callers to writeint d = findDiscountPercent(c);, which crashes for invalid codes. - Better: return
OptionalInt(orOptional<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.