Q6 of 40 · Core Java
What is autoboxing and unboxing? When does it become problematic?
Short answer
Short answer: Autoboxing is automatic conversion from a primitive to its wrapper (int → Integer); unboxing is the reverse. The compiler inserts these conversions silently. Problems arise in performance-sensitive loops (heap allocations per iteration), NullPointerExceptions when unboxing null, and unexpected == comparison behaviour with cached Integer values.
Detail
Autoboxing happens when the compiler sees a primitive where a reference type is expected — it inserts a call to the wrapper's valueOf() method. Unboxing is the reverse, inserting a call to intValue() (or equivalent). Both are transparent syntactic sugar:
Integer x = 5; // compiled as: Integer x = Integer.valueOf(5);
int y = x; // compiled as: int y = x.intValue();
Performance problem in loops: each autobox in a tight loop allocates a new heap object (unless the value is in the Integer cache). A loop that accumulates Long results instead of long can allocate millions of objects and trigger frequent GC pauses.
NullPointerException on unboxing: if a Map<String, Integer> returns null for a missing key and you assign it to an int, the compiler inserts .intValue() on null — instant NPE with a confusing stack trace that points to the assignment line, not an obvious null dereference.
Comparison trap: autoboxing + integer cache makes == on boxed values behave inconsistently (true for -128..127, false outside that range). In test code, assertions like assertEquals(expected, actual) on Integer values work correctly because JUnit calls .equals(), but ad-hoc if (a == b) comparisons can be silently wrong.
For QA automation, the main risk is test data builders that generate large datasets using boxed types where primitive arrays would be far more efficient.
// EXAMPLE
AutoboxingProblems.java
// ❌ Performance: autoboxing in loop allocates Long on every iteration
Long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i; // unboxes sum, adds i, autoboxes result back to Long
}
// ✅ Use primitive throughout
long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i; // pure primitive, no heap allocation
}
// ❌ NPE on unboxing a null from a map
Map<String, Integer> config = Map.of("timeout", 30);
int missing = config.get("retries"); // null.intValue() → NullPointerException
// ✅ Provide a default
int missing = config.getOrDefault("retries", 3);
// ❌ == comparison trap on boxed Integer
Integer a = 1000, b = 1000;
if (a == b) { /* false — different heap objects */ }
// ✅ Always use equals() for boxed types
if (a.equals(b)) { /* correct */ }