What correctness bugs arise from boxing when you use wrapper types like Integer in generic collections?
answer
- null Integer -> unboxing NPE
- == compares identity not value
- Integer cache -128..127
- List.remove(int) index vs remove(Object) value
- Use equals / getOrDefault / primitives
basics
~20 sTwo big bugs. First, an Integer can be null, and unboxing a null throws NullPointerException. Second, comparing boxed numbers with == compares object identity, not value, so it can give wrong results; use equals() or compare as primitives.
solid answer
~50 sBecause generics force you to use wrapper types, you inherit two classic boxing bugs. (1) Null unboxing: a List<Integer> element or a Map.get() miss can be null, and assigning it to an int (or doing arithmetic) auto-unboxes it, throwing NullPointerException at a line that looks like simple math. (2) Identity vs value equality: == on two Integers compares references, not values. Because Integer.valueOf caches -128..127, two boxed 100s are == true but two boxed 1000s are == false, so == 'works' in tests with small numbers and fails in production. Always use .equals() or unbox to int before comparing. A third, subtler issue: silent autoboxing can pick a surprising overload (e.g. List.remove(int) the index vs remove(Object) the value). Defenses: prefer primitives where possible, use Objects.equals/equals(), guard map lookups (getOrDefault), and let static analysis flag boxing.
code
java · 14 lines// Bug 1: null unboxing NPE
Map<String, Integer> counts = new HashMap<>();
// int c = counts.get("x"); // NPE: get() returns null
int c = counts.getOrDefault("x", 0); // safe
// Bug 2: identity vs value
Integer a = 1000, b = 1000;
boolean wrong = (a == b); // false!
boolean right = a.equals(b); // true
// Bug 3: overload surprise
List<Integer> list = new ArrayList<>(List.of(10, 20, 30));
list.remove(1); // removes index 1 (the 20)
list.remove(Integer.valueOf(20));// removes the VALUE 20go deeper
Knows you should use equals() not == for Integers, and that a null can cause a crash.
Can explain null-unboxing NPEs, the Integer cache (-128..127) behind the == pitfall, and uses getOrDefault/equals defensively.
Also catches overload-resolution traps (List.remove), reasons about all wrapper caches, and sets coding standards/static-analysis rules to prevent these.
Drives org-wide conventions and tooling (lint rules, code review checklists) to eliminate boxing-equality/null classes of bugs, and weighs nullability annotations/Optional usage.
## Why these bugs even exist Generics can't take primitives, so a 'collection of numbers' must be `List<Integer>`, `Map<String,Long>`, etc. That forces **boxing** (primitive→wrapper) and brings two properties of wrapper objects that primitives never had: they can be **null**, and they have **object identity** distinct from their value. Both are footguns. ## Bug 1 — NullPointerException on unboxing A primitive `int` is always a real number. A wrapper `Integer` is a reference and can be `null`. ```java Map<String, Integer> counts = new HashMap<>(); int c = counts.get("missing"); // get returns null -> unboxing null -> NPE ``` The NPE fires on a line that *looks* like a plain assignment or arithmetic, which makes it confusing. Any place an `Integer` is auto-unboxed to `int` — assignment, `+`, `<`, ternary mixing `int` and `Integer` — can throw if the value is `null`. Defenses: `getOrDefault("missing", 0)`, null checks, `Optional`, or `Integer` (not `int`) on the receiving side when null is meaningful. ## Bug 2 — `==` compares identity, not value `==` on reference types compares **whether they are the same object**, not whether they hold the same value. ```java Integer a = 100, b = 100; Integer x = 1000, y = 1000; System.out.println(a == b); // true System.out.println(x == y); // false (!) ``` Why the inconsistency? **`Integer.valueOf` caches the range -128..127** (autoboxing uses `valueOf`). So small values return the *same cached object* (reference-equal), while larger values allocate distinct objects. The bug is insidious: unit tests with small numbers pass, production data with large numbers fails. **Always use `.equals()`** (or `Objects.equals` for null-safety, or compare unboxed `int`s). ## Bug 3 — overload resolution surprises Autoboxing interacts with overloads. The classic: `List<Integer>` has both `remove(int index)` and `remove(Object o)`. ```java List<Integer> list = new ArrayList<>(List.of(10, 20, 30)); list.remove(1); // removes INDEX 1 -> the value 20 list.remove(Integer.valueOf(1)); // removes the VALUE 1 (none here) ``` `remove(1)` calls `remove(int)` (no boxing needed), removing by *index*, not by value — a frequent surprise. ## Other related hazards - **Performance** (covered elsewhere): boxing in hot loops creates garbage. - **`compareTo`/sorting**: fine on wrappers, but mixing `null` elements throws. - **Caching of other wrappers:** `Boolean`, `Byte`, `Short`, `Character`, and `Long` also cache small/common values; never rely on `==` for any wrapper. ## How to defend 1. Use primitives (`int`) wherever a value can't legitimately be absent. 2. Compare with `.equals()` / `Objects.equals()`, or unbox to primitives first. 3. Use `getOrDefault`, null checks, or `Optional` around lookups that can miss. 4. Be deliberate with overloaded methods like `List.remove` — use `Integer.valueOf(x)` to remove by value. 5. Enable IDE/static-analysis warnings for autoboxing and `==`-on-boxed. With these, any level can both *explain* the bugs (null unboxing, identity `==` with the -128..127 cache, overload surprises) and *avoid* them.
- How do you safely remove the value 1 (not index 1) from a List<Integer>?Call list.remove(Integer.valueOf(1)) so the remove(Object) overload is selected; list.remove(1) would call remove(int) and delete index 1 instead.
- Why does == sometimes work for boxed Integers in tests but fail in production?Integer.valueOf caches -128..127, so small test values are reference-equal and == returns true; larger production values allocate distinct objects and == returns false. Use equals().
A boxed Integer is a labeled box: two boxes can carry the same number inside (equal value) yet be different boxes (== false). And an empty box (null) crashes you when you try to read the number out of it.
saying these in an interview costs you the question
- Using == to compare Integer/Long/Boolean values
- Assigning a map/list lookup result straight into an int without null handling
- Assuming list.remove(intValue) removes by value
- Believing autoboxing is purely free with no semantic effects