Wrapper Classes and Autoboxing in Java
Java has two kinds of types: primitives like int and double, and objects like String. Primitives are fast and lightweight, but they are not objects, so they cannot be stored in collections or used with generics.
A wrapper class solves this. It wraps a primitive value inside an object.
int number = 42; // primitiveInteger boxed = 42; // wrapper object holding the same valueThe Eight Wrapper Classes
Every primitive type has one matching wrapper in java.lang, so no import is needed.
| Primitive | Wrapper class | Size |
|---|---|---|
byte | Byte | 8 bit |
short | Short | 16 bit |
int | Integer | 32 bit |
long | Long | 64 bit |
float | Float | 32 bit |
double | Double | 64 bit |
char | Character | 16 bit |
boolean | Boolean | 1 bit |
Note the two names that do not simply add a capital letter: int becomes Integer and char becomes Character.
Why Wrapper Classes Exist
- Collections and generics only accept objects. You cannot write
List<int>. - They can be
null. A primitiveintis always0by default, but anIntegercan say “no value yet”. - They carry useful methods and constants, such as parsing text and reporting minimum and maximum values.
List<Integer> scores = new ArrayList<>();scores.add(90); // int is boxed into Integer automaticallyscores.add(75);
System.out.println(scores); // [90, 75]Integer missing = null; // allowed// int broken = null; // compile errorAutoboxing and Unboxing
Autoboxing is the compiler turning a primitive into its wrapper. Unboxing is the reverse.
Integer boxed = 10; // autoboxing: Integer.valueOf(10)int unboxed = boxed; // unboxing: boxed.intValue()It happens in method calls and arithmetic too.
Integer a = 5;Integer b = 7;
int sum = a + b; // both unboxed, added, result 12Integer total = a + b; // added, then the result is boxed againUnboxing a null Throws
This is the most common wrapper bug. Unboxing calls a method on the object, so a null wrapper throws NullPointerException.
Map<String, Integer> stock = new HashMap<>();
int count = stock.get("pens"); // NullPointerException, the key is missingGuard against it before unboxing:
Integer value = stock.get("pens");int count = (value != null) ? value : 0;
// or in one lineint safe = stock.getOrDefault("pens", 0);Comparing Wrapper Objects
== compares references for objects, not values. Use equals() instead.
Integer x = 1000;Integer y = 1000;
System.out.println(x == y); // false, two different objectsSystem.out.println(x.equals(y)); // trueThe Integer Cache Surprise
Java caches boxed integers from -128 to 127, so small values reuse the same object and == appears to work.
Integer a = 100;Integer b = 100;System.out.println(a == b); // true, both come from the cache
Integer c = 200;Integer d = 200;System.out.println(c == d); // false, outside the cache rangeThe rule is simple: never use == on wrapper objects. Compare with equals(), or unbox one side first.
System.out.println(c.intValue() == d.intValue()); // trueConverting Between Types
String to Primitive
Each numeric wrapper has a parseXxx() method that returns a primitive.
int age = Integer.parseInt("30");double price = Double.parseDouble("19.99");boolean flag = Boolean.parseBoolean("true");long big = Long.parseLong("9000000000");Text that is not a valid number throws NumberFormatException, so parse user input inside a try block.
try { int value = Integer.parseInt("12a");} catch (NumberFormatException e) { System.out.println("Not a number: " + e.getMessage());}String to Wrapper
valueOf() does the same job but returns the wrapper object and uses the cache.
Integer boxed = Integer.valueOf("30");Integer fromInt = Integer.valueOf(30);Prefer valueOf() over the deprecated new Integer(30) constructor, which always creates a fresh object.
Primitive to String
int number = 255;
String a = String.valueOf(number);String b = Integer.toString(number);String c = number + ""; // works, but least readableUseful Methods and Constants
System.out.println(Integer.MAX_VALUE); // 2147483647System.out.println(Integer.MIN_VALUE); // -2147483648System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
System.out.println(Integer.compare(5, 9)); // -1System.out.println(Integer.sum(5, 9)); // 14System.out.println(Integer.max(5, 9)); // 9
System.out.println(Integer.toBinaryString(10)); // 1010System.out.println(Integer.toHexString(255)); // ffCharacter is handy for validating text one letter at a time.
System.out.println(Character.isDigit('7')); // trueSystem.out.println(Character.isLetter('a')); // trueSystem.out.println(Character.isUpperCase('a')); // falseSystem.out.println(Character.toUpperCase('a')); // ASystem.out.println(Character.isWhitespace(' ')); // trueString input = "abc123";long digits = input.chars().filter(Character::isDigit).count();System.out.println(digits); // 3Wrappers Are Immutable
A wrapper object never changes its value. Any operation creates a new object.
Integer count = 10;Integer other = count;
count = count + 5; // a new Integer is created
System.out.println(count); // 15System.out.println(other); // 10, untouchedThis also means wrappers are safe to share between threads.
The Cost of Boxing
Boxing allocates an object, so a tight loop over wrappers is far slower than the same loop over primitives.
// slow: every iteration boxes and unboxesLong sum = 0L;for (long i = 0; i < 1_000_000; i++) { sum += i;}
// fast: pure primitive arithmeticlong fastSum = 0L;for (long i = 0; i < 1_000_000; i++) { fastSum += i;}For streams, the primitive variants avoid boxing entirely.
int total = IntStream.rangeClosed(1, 100).sum(); // no Integer objects createdCommon Mistakes
- Comparing wrappers with
==and being fooled by the-128to127cache. - Unboxing a
nullreturned from aMapor a database column. - Using wrappers in hot loops where primitives would do.
- Calling
new Integer(5)instead ofInteger.valueOf(5). - Forgetting that
Integer.parseInt()throws on empty or malformed text.
Best Practices
- Use primitives by default, and wrappers only when an object is required.
- Compare wrapper values with
equals()or by unboxing explicitly. - Use
getOrDefault()or a null check before unboxing map values. - Prefer
parseInt()when you need a primitive andvalueOf()when you need an object. - Reach for
IntStream,LongStream, andDoubleStreamto keep numeric pipelines unboxed.
Quick Summary
| Concept | Meaning |
|---|---|
| Wrapper class | An object that holds a primitive value |
| Autoboxing | Primitive converted to wrapper automatically |
| Unboxing | Wrapper converted back to primitive |
| Integer cache | Values -128 to 127 reuse the same object |
parseXxx() | Text to primitive |
valueOf() | Text or primitive to wrapper object |
Next Steps
Wrapper classes are what let primitives live inside generics, so the next topic is the Collections Framework, where List<Integer> and Map<String, Integer> put them to work.