Wrapper Classes, Autoboxing and Unboxing in Java
Wrapper classes turn primitives into objects so they can be used with generics and collections, and the conversion is automatic but not free.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Why wrappers exist
Generics and collections work only with reference types, so List<int> is impossible. Each primitive therefore has a matching class that holds one value.
| Primitive | Wrapper |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Every wrapper is immutable and final.
Autoboxing and unboxing
Integer boxed = 42; // autoboxing: Integer.valueOf(42)
int plain = boxed; // unboxing: boxed.intValue()
List<Integer> numbers = new ArrayList<>();
numbers.add(7); // autoboxed
int first = numbers.get(0); // unboxedSince Java 5 the compiler inserts these conversions. They are convenient, and they hide three real costs: allocation, a possible NullPointerException, and surprising equality behaviour.
The equality trap
Integer a = 100, b = 100;
System.out.println(a == b); // true
Integer c = 1000, d = 1000;
System.out.println(c == d); // false
System.out.println(c.equals(d)); // trueInteger.valueOfcaches values from -128 to 127, so small numbers return the same cached object and==appears to work. Above that range each call allocates. This is why==on wrappers is a latent bug rather than an outright error, and whyequalsis the only correct comparison.
The cache exists for Boolean, Byte, Character up to 127, Short, Integer and Long in the same small range. Float and Double are never cached.
The null trap
Map<String, Integer> counts = new HashMap<>();
int value = counts.get("missing"); // NullPointerException on unboxing
int safe = counts.getOrDefault("missing", 0); // correctA wrapper can be null; a primitive cannot. Unboxing a null reference throws, and the stack trace points at an assignment that looks harmless.
Performance
// Boxes and unboxes on every iteration
Long total = 0L;
for (long i = 0; i < 1_000_000; i++) {
total += i; // total = Long.valueOf(total.longValue() + i)
}
// One primitive accumulator, no allocation
long total = 0;
for (long i = 0; i < 1_000_000; i++) {
total += i;
}The first loop allocates a million objects. Declaring the accumulator as a primitive is the entire fix, and it is a real difference in hot code.
Useful wrapper methods
int parsed = Integer.parseInt("250"); // returns int
Integer boxed = Integer.valueOf("250"); // returns Integer, prefer for caching
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.toBinaryString(10)); // 1010
System.out.println(Integer.compare(3, 9)); // negative
System.out.println(Integer.parseInt("ff", 16)); // 255
System.out.println(Double.isNaN(0.0 / 0.0)); // true
System.out.println(Character.isDigit('7'));
System.out.println(Boolean.parseBoolean("TRUE")); // truePrefer parseInt when a primitive is wanted and valueOf when an object is, so no needless boxing occurs either way.
Avoiding boxing in streams
// Boxes every element
int total = numbers.stream().mapToInt(Integer::intValue).sum();
// Primitive stream throughout, no boxing
int sum = IntStream.rangeClosed(1, 100).sum();
OptionalDouble average = IntStream.of(4, 8, 15).average();IntStream, LongStream and DoubleStream exist precisely to avoid this cost, along with OptionalInt and the primitive functional interfaces such as IntPredicate.
Common mistakes
- Comparing wrappers with
==and being misled by the small value cache. - Unboxing a
nullreturned byMap.get. - Declaring a loop accumulator as a wrapper type.
- Using
Integereverywhere out of habit when a primitive would do. - Calling
new Integer(5), which is deprecated for removal; useInteger.valueOf.
Best practices
- Use primitives unless a reference type is genuinely required.
- Always compare wrappers with
equals, or unbox both sides deliberately. - Use
getOrDefaultor an explicit null check before unboxing. - Use primitive streams and primitive functional interfaces in hot code.
- Reserve wrapper types for cases where
nullcarries real meaning, such as "not supplied".
Practice
- Explain why
Integer a = 127, b = 127; a == bis true but the same with 128 is false. - Fix
int total = map.get(key);so it cannot throw. - Rewrite a boxed accumulation loop with a primitive and describe the difference in allocation.
- When is
Integera better field type thanint? - What is printed by
System.out.println(Double.valueOf(0.0) == Double.valueOf(0.0));and why?
Conclusion
Wrappers let primitives live in the object world, and autoboxing hides the conversion. Compare with equals, guard against null, and keep primitives in loops and hot paths where the allocation matters.