Definition
Wrapper classes bridge the gap between primitives and objects:
- **Boxing** converts a primitive to its wrapper: `int 42` → `Integer.valueOf(42)` → Integer object on heap.
- **Unboxing** converts back: `Integer` → `intValue()` → `int 42`.
- **Why needed:** generics require objects (`List<Integer>`), null represents absence (`Integer score = null`), and wrappers carry utility methods (`Integer.MAX_VALUE`, `Integer.parseInt`).
- **parseInt vs valueOf:** `parseInt("42")` returns primitive `int`. `valueOf("42")` returns `Integer` object (cached for -128 to 127).Primitives are loose cash: fast to count but cannot go into the bank's filing cabinets which only accept labelled envelopes. Wrappers are those envelopes. An empty envelope (null) means "no amount recorded." parseInt counts the cash out of the envelope for arithmetic. valueOf puts it into a new envelope, or for small amounts, hands you a pre-filed envelope from a shared drawer.
Understanding the Concepts
What are the eight wrapper classes?
| Primitive | Wrapper | Example |
|---|---|---|
| boolean | Boolean | Boolean.TRUE, Boolean.valueOf(true) |
| byte | Byte | Byte.parseByte("42") |
| short | Short | Short.valueOf((short) 100) |
| char | Character | Character.isDigit('5') |
| int | Integer | Integer.parseInt("42") |
| long | Long | Long.parseLong("9876543210") |
| float | Float | Float.parseFloat("3.14") |
| double | Double | Double.valueOf("2.718") |
All wrapper classes are in java.lang (auto-imported). All are final (cannot be subclassed) and immutable (the wrapped value cannot change after construction).
Why are wrappers needed?
Reason 1: Generics and collections require objects
// Does NOT compile: generics only accept objects
// List<int> amounts = new ArrayList<>();
// Correct: use the wrapper
List<Integer> amounts = new ArrayList<>();
amounts.add(50000); // autoboxes int → Integer
amounts.add(12000);
int first = amounts.get(0); // unboxes Integer → int
This applies to every generic type: Map, Optional, Stream, etc.
Reason 2: Null represents absence
A primitive always has a value. An int defaults to 0. But sometimes 0 is a valid value and you need to distinguish "no value recorded" from "the value is zero."
public class Account {
private int transactionCount; // always has a value (0 if unset)
private Integer creditScore; // null means "no score on file"
public boolean hasCreditScore() {
return creditScore != null;
}
}
In banking, a null credit score means the bureau has no record. A score of 0 would mean the worst possible score. These are different things.
Reason 3: Utility methods and constants
Wrapper classes carry useful static methods:
// Parsing strings to numbers
int count = Integer.parseInt("42");
long timestamp = Long.parseLong("1698765432000");
double rate = Double.parseDouble("8.5");
// Constants
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Long.MIN_VALUE); // -9223372036854775808
System.out.println(Float.NaN); // NaN
System.out.println(Double.POSITIVE_INFINITY); // Infinity
// Conversion utilities
System.out.println(Integer.toBinaryString(255)); // 11111111
System.out.println(Integer.toHexString(255)); // ff
System.out.println(Integer.toOctalString(255)); // 377
// Comparison
System.out.println(Integer.compare(100, 200)); // -1 (less than)
System.out.println(Integer.max(100, 200)); // 200
// Character utilities
System.out.println(Character.isDigit('5')); // true
System.out.println(Character.isLetter('A')); // true
System.out.println(Character.toUpperCase('a')); // A
What is the difference between parseInt and valueOf?
This is a frequent interview question. The difference is the return type:
// parseInt returns a PRIMITIVE int
int primitive = Integer.parseInt("42"); // int 42 (no object created)
// valueOf returns an INTEGER OBJECT
Integer object = Integer.valueOf("42"); // Integer(42) on heap
// valueOf with int argument (not String)
Integer cached = Integer.valueOf(42); // may return cached instance
| Method | Returns | Creates object? | Uses cache? |
|---|---|---|---|
| Integer.parseInt("42") | int (primitive) | No | No |
| Integer.valueOf("42") | Integer (object) | Yes (or cached) | Yes (-128 to 127) |
| Integer.valueOf(42) | Integer (object) | Yes (or cached) | Yes (-128 to 127) |
| new Integer(42) | Integer (object) | Always new | No (deprecated Java 9) |
Rule: use parseInt when you need a primitive for arithmetic. Use valueOf when you need a wrapper for a collection or nullable field.
Every wrapper has the same pattern:
int i = Integer.parseInt("42"); // → int
long l = Long.parseLong("42"); // → long
double d = Double.parseDouble("3.14"); // → double
boolean b = Boolean.parseBoolean("true"); // → boolean
Integer io = Integer.valueOf("42"); // → Integer
Long lo = Long.valueOf("42"); // → Long
Double dObj = Double.valueOf("3.14"); // → Double
Boolean bo = Boolean.valueOf("true"); // → Boolean
What is the performance cost of wrappers?
Wrappers have overhead compared to primitives:
- Memory: An
Integerobject uses ~16 bytes (12-byte header + 4-byte int value). A primitiveintuses 4 bytes. - Allocation: Every
new Integer()or uncachedvalueOf()allocates on the heap. - GC pressure: Short-lived wrapper objects in loops create garbage.
// BAD: 1 million Integer objects created
List<Integer> amounts = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
amounts.add(i); // autoboxes → 1M Integer objects
}
// GOOD: primitive array, no objects
int[] amounts = new int[1_000_000];
for (int i = 0; i < amounts.length; i++) {
amounts[i] = i; // no boxing
}
Use primitive arrays and IntStream/LongStream for bulk numeric work.
When should you use wrapper vs primitive?
| Use primitive when | Use wrapper when |
|---|---|
| Arithmetic, loops, counters | Collections: List, Map |
| Performance-critical code | Nullable fields: Integer creditScore = null |
| The value is always present | JPA/Hibernate entities with nullable DB columns |
| Method parameters (usually) | Generic type arguments |
| Return type (usually) | Optional return: Optional |
What about records and wrappers?
Java records can use either primitives or wrappers as components:
// Primitive component: cannot be null, defaults to 0 if not set
public record Transaction(String id, int amount) {}
// Wrapper component: can be null
public record CreditCheck(String accountId, Integer score) {}
var tx = new Transaction("TX001", 50000); // amount is always present
var check = new CreditCheck("SAV2001", null); // score may be absent
What are common mistakes?
- Using
new Integer(42)instead ofInteger.valueOf(42). The constructor is deprecated since Java 9 and always creates a new object, bypassing the cache. - Comparing wrappers with
==. Works for small cached values, fails for large values. Always use.equals(). - Using
Integeras a loop accumulator. Creates a new object every iteration. Useintorlong. - Unboxing a null wrapper. Throws
NullPointerException. Always null-check nullable wrappers before unboxing.
What should a senior engineer know?
- All wrappers are immutable. You cannot change the value inside an Integer. Operations like
integer + 1create a new Integer. - All wrappers implement Comparable.
Integer.compareTo()enables natural ordering in sorted collections. - Number is the abstract superclass of
Integer,Long,Float,Double,Short,Byte. It definesintValue(),longValue(),floatValue(),doubleValue(). - Project Valhalla aims to allow primitive types in generics (
Listdirectly), which would eliminate the need for wrapper boxing in collections. - JPA mapping: nullable database columns (
INT NULL) should useIntegerin the entity, notint. Usingintmeans NULL maps to 0, losing the null semantics.
Visual Overview
Primitive ←→ Wrapper mapping:
boolean ←→ Boolean char ←→ Character
byte ←→ Byte int ←→ Integer
short ←→ Short long ←→ Long
float ←→ Float double ←→ Double
parseInt vs valueOf:
Integer.parseInt("42") → int 42 (primitive, no object)
Integer.valueOf("42") → Integer(42) (object, may be cached)
Integer.valueOf(42) → Integer(42) (object, cached if -128..127)
When to use which:
primitive int → arithmetic, loops, counters, return types
Integer wrapper → List<Integer>, Map values, nullable fields, Optional
Key Takeaway
Every primitive in Java has a corresponding wrapper class that boxes the value into an object. Boolean for boolean, Byte for byte, Character for char, Integer for int, Long for long, Float for float, Double for double, and Short for short. These wrappers exist because generics and collections only accept objects, because a wrapper reference can be null to represent absence while a primitive cannot, and because wrappers carry utility methods like parseInt, compare, MAX_VALUE, and toBinaryString.
The key distinction is between parseInt and valueOf. parseInt takes a string and returns a primitive int with no object created, so you use it when you need a raw number for arithmetic. valueOf takes a string or int and returns an Integer object, consulting the cache for values -128 to 127. You use valueOf when you need a wrapper for a nullable field or a collection element.
Wrappers have real costs: each Integer uses about 16 bytes versus 4 bytes for a primitive int, and autoboxing in loops creates millions of garbage objects. The rule is to use primitives for arithmetic and performance-critical code, and wrappers only when you genuinely need an object – for collections, nullable fields, and generic type arguments.
One sentence to remember: wrappers box primitives into objects for collections, generics, and nullability; use parseInt for arithmetic and valueOf for objects; and never compare wrappers with == because the cache only covers -128 to 127.
---