Each primitive type has a wrapper class in java.lang: Byte, Short, Integer, Long, Float, Double, Character and Boolean. Wrappers exist because collections and generics work only with objects, so you cannot write List<int>. A wrapper object is immutable and can also be null, which a primitive cannot.
Autoboxing is the compiler automatically converting a primitive to its wrapper, and unboxing is the reverse. When you write Integer n = 5; the compiler inserts Integer.valueOf(5), and when you write int m = n; it inserts n.intValue(). Unboxing a null reference therefore throws a NullPointerException at runtime, a frequent exam scenario with Map.get returning null into an int variable.
Know the difference between the two conversion families. Integer.parseInt("42") returns a primitive int, while Integer.valueOf("42") returns an Integer object. The wrapper constructors such as new Integer(5) are deprecated and marked for removal, so modern code and exam answers use valueOf or autoboxing. Boxing only goes to the matching wrapper: Long x = 5; does not compile, because 5 is an int and would need to become an Integer, which is not a Long.
The == operator on two wrapper references compares identity (whether they are the same object), not value. Integer.valueOf keeps a cache of Integer objects for at least the range -128 to 127, and autoboxing uses valueOf, so two boxed values in that range usually point at the same cached object. Outside that range each boxing typically creates a new object. That is why Integer a = 127, b = 127; a == b is true but the same test with 128 is false. Short, Long and Byte cache the same range, Character caches 0 to 127, and Boolean has only TRUE and FALSE. The fix is simple: compare wrapper values with equals.
Mixing a wrapper and a primitive in == is different: the wrapper is unboxed, so the comparison is numeric and the cache does not matter. Also watch equals across types. Long.valueOf(1).equals(1) is false, because the argument is boxed to an Integer, and Long.equals returns false for anything that is not a Long.
Integer a = 100, b = 100;
Integer c = 1000, d = 1000;
System.out.println(a == b); // true (cached)
System.out.println(c == d); // false (different objects)
System.out.println(c.equals(d)); // true
int e = 1000;
System.out.println(c == e); // true (c is unboxed)
Overload resolution also involves boxing. The compiler first looks for an exact or widening match, then tries boxing and unboxing, and only then varargs. So given m(long) and m(Integer), the call m(5) picks m(long), because widening beats boxing.
Key terms
- Wrapper class
- An immutable class such as Integer or Double that holds one primitive value as an object.
- Autoboxing
- The automatic conversion of a primitive to its wrapper, done by the compiler through valueOf.
- Unboxing
- The automatic conversion of a wrapper to its primitive; throws NullPointerException if the reference is null.
- Integer cache
- A pool of Integer objects for at least -128 to 127 that Integer.valueOf reuses, which makes == appear to work for small values.
A shop counts orders per customer in a Map<String, Integer>. The code int count = counts.get(id); works in testing, then crashes in production with a NullPointerException the first time a new customer appears, because get returns null and the unboxing fails. Using getOrDefault(id, 0) avoids it.
Check yourself
What does Integer x = 128, y = 128; System.out.println(x == y); print?
Usually false. 128 is outside the guaranteed cache range, so each boxing creates a separate object and == compares identity.
Does Long total = 10; compile?
No. 10 is an int, and autoboxing only produces an Integer, which is not assignable to Long. Write 10L.
What happens when Integer n = null; int m = n; runs?
It compiles but throws a NullPointerException, because unboxing calls intValue() on null.