What hidden costs and pitfalls does autoboxing of wrapper classes introduce, and how would you avoid them in performance-sensitive or correctness-sensitive code?
answer
- Unbox null → NPE (hidden intValue())
- Boxing in loops → millions of objects
- == compares identity; cache makes it inconsistent
- Prefer primitives + IntStream/LongStream
- Guard nulls; compare with equals()
basics
~20 sAutoboxing silently turns primitives into objects, which can (1) throw NullPointerException when a null wrapper is unboxed, (2) allocate many objects in loops hurting performance, and (3) make == compare object identity instead of value. Use primitives where possible and equals() to compare.
solid answer
~50 sAutoboxing is the compiler converting between primitives and wrappers automatically. It is convenient but hides three costs. First, unboxing a null wrapper (e.g. Integer i = null; int x = i;) throws a NullPointerException at the implicit i.intValue() call — a common surprise with map.get returning null or nullable DB columns. Second, boxing allocates: a loop like Long sum = 0L; sum += x; reboxes on every iteration, creating millions of objects and crushing throughput — use a primitive long instead. Third, == on wrappers compares references, and because of the -128..127 cache it can be true for small values but false for large ones, so always compare with equals() or by unboxing. Mitigations: prefer primitives in hot paths and accumulators, use primitive-specialized streams (IntStream/LongStream) and collections where available, guard nullable wrappers before unboxing, and never use == on wrappers.
code
java · 14 lines// Pitfall 1: NPE on unboxing null
Integer maybe = map.get("absent"); // null
// int n = maybe; // NullPointerException (hidden maybe.intValue())
// Pitfall 2: boxing in a loop (slow, lots of garbage)
Long slow = 0L;
for (long x = 0; x < 10_000_000L; x++) slow += x; // reboxes each iteration
long fast = 0L;
for (long x = 0; x < 10_000_000L; x++) fast += x; // primitive: no boxing
// Pitfall 3: == compares identity, not value
Integer a = 128, b = 128;
System.out.println(a == b); // false (two objects)
System.out.println(a.equals(b)); // true (compare values)go deeper
Knows autoboxing converts between int and Integer automatically and that unboxing null can crash.
Explains the NPE-on-null, the == identity trap with the cache, and that boxing in loops is wasteful.
Quantifies the allocation/GC cost, prescribes primitives and IntStream/LongStream, and consistently guards nulls and avoids ==.
Reasons about boxing's impact on throughput/latency and GC at scale, escape-analysis limits, primitive-specialized data structures, and how Valhalla value classes would remove the overhead.
## What autoboxing is Since Java 5 the compiler automatically converts between a primitive and its wrapper: - **Autoboxing:** primitive → wrapper. `Integer i = 5;` compiles to `Integer i = Integer.valueOf(5);`. - **Unboxing:** wrapper → primitive. `int n = i;` compiles to `int n = i.intValue();`. This happens silently in assignments, method calls, collections, arithmetic, and comparisons. The convenience hides three real costs. ## Pitfall 1 — NullPointerException on unboxing null A wrapper reference can be `null`; a primitive cannot. When the compiler inserts an implicit `intValue()` (or `longValue()`, etc.) on a `null` wrapper, you get a **`NullPointerException`** at runtime — and it is easy to miss because the unboxing is invisible in the source. ```java Integer count = map.get("missing"); // returns null int c = count; // NPE: hidden count.intValue() if (someInteger == 5) { ... } // NPE if someInteger is null (unboxed to compare) ``` This bites with `Map.get` (returns null for absent keys), nullable DB/JSON fields, and ternaries mixing a wrapper and a primitive. ## Pitfall 2 — allocation cost in loops Each box creates (or fetches) an object. In a loop that accumulates into a **wrapper**, every iteration unboxes, adds, and **reboxes** a new object: ```java Long sum = 0L; // wrapper accumulator — BAD for (long x = 0; x < 100_000_000; x++) sum += x; // reboxes every iteration ``` This allocates ~100 million short-lived `Long` objects, generating enormous GC pressure and running dramatically slower than the primitive version (`long sum = 0L;`). The same applies to wrapper-heavy collections (`List<Integer>`) versus primitive arrays. ## Pitfall 3 — identity vs value with == `==` on wrappers compares **references**, not values. Because `valueOf` caches **-128..127**, small equal values can share an instance (`==` true) while large equal values are distinct objects (`==` false): ```java Integer a = 127, b = 127; a == b; // true (cached) Integer c = 128, d = 128; c == d; // false (two objects!) ``` This produces bugs that 'work' in tests with small numbers and fail in production. Always compare wrapper values with `.equals()` or unbox to primitives. ## How to avoid the costs 1. **Prefer primitives**, especially for loop counters, accumulators, and hot paths. Use a wrapper only when you genuinely need null, generics, or collection membership. 2. **Use primitive-specialized APIs:** `IntStream`/`LongStream`/`DoubleStream` instead of `Stream<Integer>`; `int[]` instead of `List<Integer>` when feasible; specialized maps (e.g. from libraries) for primitive keys/values. 3. **Guard nullable wrappers** before unboxing: check for null, or use `Optional`, or default with something like `Objects.requireNonNullElse`. 4. **Never use == on wrappers** — use `.equals()` or unbox both sides explicitly. 5. **Watch mixed-type expressions:** if one operand is a primitive and the other a wrapper, the wrapper is unboxed (NPE risk) — keep types consistent. ## Takeaway Autoboxing is sugar that hides allocation, null-unboxing NPEs, and identity comparison. Default to primitives, reach for wrappers deliberately, and never trust `==` to compare wrapper values.
- Why does Integer a = 127, b = 127; a == b give true but the same with 128 gives false?valueOf caches -128..127, so 127 returns the same shared instance (== true), while 128 allocates two distinct objects (== false). Use equals() to compare values.
- How would you speed up Long sum = 0L; sum += x; in a hot loop?Use a primitive accumulator: long sum = 0L;. This removes the per-iteration boxing/unboxing and the millions of allocations, eliminating GC pressure.
- Where does an NPE commonly hide with autoboxing?Unboxing the result of Map.get (null for a missing key), a nullable field, or a ternary/comparison mixing a possibly-null wrapper with a primitive.
saying these in an interview costs you the question
- Believing autoboxing is free — it allocates and adds GC pressure
- Using == to compare two Integer objects
- Unboxing a wrapper without null-checking
- Accumulating into a wrapper inside a tight loop
- Assuming primitive and wrapper behave identically in mixed expressions