Integer wraps int, Double wraps double, and so on. You need them because generics and collections work only with objects (List<int> is illegal), and because a wrapper can be null. Java converts between the two automatically, which is called autoboxing (primitive to wrapper) and unboxing (wrapper to primitive). That convenience hides several traps: == on boxed values, NullPointerException when unboxing, and equals that compares types.
| Primitive | Wrapper | Range (from the program below) |
|---|---|---|
| byte | Byte | -128 .. 127 |
| short | Short | -32768 .. 32767 |
| int | Integer | -2147483648 .. 2147483647 |
| long | Long | -9223372036854775808 .. 9223372036854775807 |
| float | Float | 1.4E-45 .. 3.4028235E38 (smallest positive .. largest) |
| double | Double | 4.9E-324 .. 1.7976931348623157E308 (smallest positive .. largest) |
| char | Character | 0 .. 65535 |
| boolean | Boolean | false, true |
Tested against. Temurin 25.0.4.1. Output comes from two programs that are not in a repository: download WrapperTraps.java and download Cache.java. Compile withjavac -gso null-pointer messages name the variable. The cache range forIntegeris configurable, so the 127 and 128 results assume default settings.
Autoboxing is just a method call
The compiler rewrites boxing as Integer.valueOf(int) and unboxing as intValue(). There is no magic, which also means every boxing can allocate an object and every unboxing can throw.
Integer boxed = 42; // Integer.valueOf(42)
int unboxed = boxed; // boxed.intValue()
boxed=42 unboxed=42
Download: WrapperTraps.java.
The Integer cache: why 127 == 127 but 128 != 128
Integer.valueOf returns a shared, pre-built object for values from -128 to 127 and creates a new one otherwise. Two boxed references to a cached value are the same object; outside the range they are different objects with the same value, and == compares references.
Integer a = 127, b = 127, c = 128, d = 128;
a == b // true
c == d // false
c.equals(d) // true
127 == 127 : true
128 == 128 : false equals: true
Integer.valueOf(127) == Integer.valueOf(127): true
Long cache also -128..127: true / false
Boolean and Byte are cached too : true true
Double is never cached : false
Source: WrapperTraps.java. Long, Short and Character follow similar caching rules, Boolean and Byte are always cached, and Double and Float never are. The rule for comparing boxed values is always the same: use equals (or unbox to a primitive), never ==.
The upper bound is tunable. Run Cache.java twice:
$ javac -g wrapper/Cache.java && java Cache (25.0.4.1)
1000 == 1000 : false
$ javac -g wrapper/Cache.java && java -XX:AutoBoxCacheMax=2000 Cache (25.0.4.1)
1000 == 1000 : true
The flag changes the answer, which is exactly why code that depends on == for boxed values is a bug even when it appears to work. Download: Cache.java.
Going deeper: wrappers and Project Valhalla
Boxing costs an allocation and a pointer hop, and a List<Integer> is a list of references. JEP 401 proposes making Integer and the other wrappers value classes, which would let the JVM flatten and inline them and would change what == means for them. It is a preview feature proposal, not something to code against; read the value classes guide.
Unboxing null throws NullPointerException
A wrapper can be null; a primitive cannot. Assigning a null wrapper to a primitive, or using it in arithmetic, calls intValue() on null.
Map<String, Integer> stock = new HashMap<>();
int n = stock.get("pens"); // NPE
Integer r = flag ? stock.get("pens") : 0; // NPE too
Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
ternary: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
Source: WrapperTraps.java. The second case is the sneaky one: in flag ? Integer : int the ternary’s type becomes int, so the Integer branch is unboxed even though you assign the result to an Integer. The fix is stock.getOrDefault("pens", 0) or an explicit null check. Both are map lookups, as covered in how HashMap works; for the same trap with Optional see the Optional guide.
equals() checks the type, == can unbox
Each wrapper’s equals returns false for any other wrapper type. An int literal boxes to Integer, so it never equals a Long. But == between a wrapper and a primitive unboxes the wrapper and compares numbers.
Long.valueOf(5).equals(5) : false (5 boxes to Integer)
Long.valueOf(5).equals(5L) : true
Integer 5 == long 5L (unboxes): true
Source: WrapperTraps.java. This shows up in practice as a Map<Long, String> lookup with map.get(5): the key is an Integer, so the lookup misses and returns null with no error.
Double: NaN and negative zero
Primitive == follows the IEEE 754 rules, while Double.equals compares the bit patterns so that it stays consistent with hashing. The two disagree in exactly two cases.
NaN == NaN : false
Double.valueOf(NaN).equals(NaN): true
0.0 == -0.0 : true
Double.valueOf(0.0).equals(-0.0): false
Source: WrapperTraps.java. Consequences: a NaN can be found in a HashSet<Double> although NaN != NaN, and 0.0 and -0.0 count as two different keys. Use Double.compare to get a total order.
Parsing and the useful static methods
parseInt("ff", 16) = 255, toBinaryString(10) = 1010, MAX+1 = -2147483648
parseInt("12a"): For input string: "12a"
Integer.compare(3, 7) = -1, Integer.sum(3, 7) = 10
Character.isDigit('7')=true getNumericValue('z')=35
Source: WrapperTraps.java. Notes: Integer.MAX_VALUE + 1 silently wraps to the minimum; use Math.addExact to fail loudly. parseInt throws NumberFormatException on bad input, so validate or catch it. Integer.compare beats subtraction for comparators because a - b can overflow; see Comparable vs Comparator. Wrapper values are also immutable and safe as map keys; see immutable collections and generics for why collections need them.
Boxing in loops and streams
A loop that sums into a boxed Long allocates a new object on every iteration. Use a primitive accumulator, or the primitive streams (IntStream, LongStream) that avoid boxing entirely; the details are in the Streams guide.
Common mistakes
Comparing boxed values with ==
It works for -128..127 and fails above, as the 127 and 128 output shows. Use equals.
Unboxing a value that may be null
Map lookups and database columns are the usual sources. Use getOrDefault or check for null first.
Mixing Integer and Long keys
equals is type strict, so map.get(5) never finds a Long key 5. Use 5L.
Using new Integer(…)
The constructors are deprecated for removal. Use Integer.valueOf or autoboxing.
Boxed accumulators in hot loops
Each addition allocates. Prefer primitives inside the loop.
Common interview questions
Why does Integer 127 == 127 print true but 128 == 128 print false?
Integer.valueOf reuses cached objects for -128 to 127, so both references point to one object. 128 creates two objects and == compares references. Use equals.
What is autoboxing and how does the compiler do it?
Automatic conversion between a primitive and its wrapper. The compiler inserts Integer.valueOf(int) for boxing and intValue() for unboxing.
Why can’t generics use primitives?
Type arguments must be reference types because generics are erased to Object. So you use List<Integer>, paying for boxing. See the generics guide.
When does autoboxing throw a NullPointerException?
When a null wrapper is unboxed: assigned to a primitive, used in arithmetic, or pulled through a ternary that unifies to a primitive type.
Are wrapper classes immutable?
Yes. Every operation yields a new object, which makes them safe as map keys and between threads. More in the string programs interview post for the same idea applied to String.
FAQ
What are the eight wrapper classes in Java?
Byte, Short, Integer, Long, Float, Double, Character and Boolean.
What is the range of the Integer cache?
-128 to 127 by default; the upper bound can be raised with -XX:AutoBoxCacheMax, as the Cache.java run shows.
Should I use int or Integer?
Use int by default. Use Integer when you need null, a generic type argument, or a collection element.
The rule of thumb. Use primitives for local arithmetic, wrappers where an object is required, andequals(never==) to compare wrappers. Related: enums, lambda expressions and collections interview questions.
Further reading
- Integer API documentation
- JLS 5.1.7: Boxing Conversion
- WrapperTraps.java and Cache.java (the demo programs)
No Comments yet!