Skip to content
elephantoo

Wrapper classes & autoboxing

Lesson 23 of 43 11 min read

Integer, Double and friends, parsing, autoboxing/unboxing, caching and null pitfalls.


Java has two worlds of types: the eight primitives (int, double, boolean...) and objects. Many APIs, especially collections and generics, only work with objects. Wrapper classes bridge the gap: each primitive has a matching class that wraps a single value in an object.

The eight wrappers#

PrimitiveWrapperExample helpers
byteByteByte.parseByte
shortShortShort.parseShort
intIntegerInteger.parseInt, Integer.MAX_VALUE, Integer.toBinaryString
longLongLong.parseLong
floatFloatFloat.parseFloat
doubleDoubleDouble.parseDouble, Double.isNaN
charCharacterCharacter.isDigit, Character.toUpperCase
booleanBooleanBoolean.parseBoolean

All wrappers live in java.lang (no import needed), are immutable, and are final.

Why wrappers exist#

WhyWrappers.java
import java.util.ArrayList;
import java.util.List;

public class WhyWrappers {
    public static void main(String[] args) {
        // List<int> scores = new ArrayList<>();   // compile error: unexpected type
        List<Integer> scores = new ArrayList<>();
        scores.add(90);          // int 90 is autoboxed to Integer
        scores.add(75);
        int first = scores.get(0);   // Integer auto-unboxed to int
        System.out.println(first + scores.get(1));

        Integer maybeAge = null;     // wrappers can represent "no value"
        System.out.println(maybeAge == null ? "age unknown" : "age " + maybeAge);
    }
}
Output
165
age unknown

Wrappers are needed for:

  1. Collections and generics: List<Integer>, Map<String, Double>.
  2. Nullability: an Integer can be null ("unknown"), an int cannot. Database columns that allow NULL map naturally to wrappers.
  3. Utility methods and constants: parsing, conversions, MIN_VALUE/MAX_VALUE.

Autoboxing and unboxing#

Since Java 5 the compiler converts automatically:

  • Autoboxing: primitive → wrapper (Integer x = 5; becomes Integer.valueOf(5)).
  • Unboxing: wrapper → primitive (int y = x; becomes x.intValue()).
Boxing.java
public class Boxing {
    static double average(Integer a, Integer b) {   // accepts ints via autoboxing
        return (a + b) / 2.0;                       // unboxed for arithmetic
    }

    public static void main(String[] args) {
        Integer boxed = 42;            // autoboxing
        int plain = boxed;             // unboxing
        Integer sum = boxed + plain;   // unbox, add, box again
        System.out.println(sum);
        System.out.println(average(3, 4));

        Double price = 9.99;
        double withTax = price * 1.18;
        System.out.printf("%.2f%n", withTax);
    }
}
Output
84
3.5
11.79

It is convenient, but each box is a real object on the heap, which matters in tight loops.

Parsing and converting#

The wrappers are the standard way to turn text into numbers and back:

Parsing.java
public class Parsing {
    public static void main(String[] args) {
        int n = Integer.parseInt("123");          // returns a primitive
        Integer boxed = Integer.valueOf("456");   // returns a wrapper (may be cached)
        double d = Double.parseDouble("3.75");
        boolean flag = Boolean.parseBoolean("TRUE");   // case-insensitive; anything else is false
        long big = Long.parseLong("9000000000");

        System.out.println(n + boxed);
        System.out.println(d + " " + flag + " " + big);

        System.out.println(Integer.parseInt("ff", 16));     // hexadecimal
        System.out.println(Integer.parseInt("1010", 2));    // binary
        System.out.println(Integer.toHexString(255) + " " + Integer.toBinaryString(10));
        System.out.println(String.valueOf(7.5) + Integer.toString(8));

        try {
            Integer.parseInt("12.5");
        } catch (NumberFormatException e) {
            System.out.println("Bad input: " + e.getMessage());
        }
    }
}
Output
579
3.75 true 9000000000
255
10
ff 1010
7.58
Bad input: For input string: "12.5"

Note that parseInt("12.5") fails: an int has no fractional part. Use Double.parseDouble for decimals.

Useful constants and helpers#

Helpers.java
public class Helpers {
    public static void main(String[] args) {
        System.out.println(Integer.MAX_VALUE + " " + Integer.MIN_VALUE);
        System.out.println(Long.MAX_VALUE);
        System.out.println(Integer.compare(3, 7));          // negative, zero or positive
        System.out.println(Integer.max(3, 7) + " " + Integer.sum(3, 7));
        System.out.println(Double.isNaN(0.0 / 0.0));
        System.out.println(Character.isLetterOrDigit('_') + " " + Character.getNumericValue('7'));
        System.out.println(Integer.bitCount(255));
    }
}
Output
2147483647 -2147483648
9223372036854775807
-1
7 10
true
false 7
8

Integer.compare is especially useful when writing comparators (see the Comparable vs Comparator lesson). Never compare with subtraction (a - b), which can overflow.

Trap 1: == on wrappers#

== on two wrapper objects compares references, not values. To save memory, Integer.valueOf (and therefore autoboxing) caches values from -128 to 127:

EqualsTrap.java
public class EqualsTrap {
    public static void main(String[] args) {
        Integer a = 127, b = 127;
        Integer c = 1000, d = 1000;
        System.out.println(a == b);        // same cached object
        System.out.println(c == d);        // two different objects!
        System.out.println(c.equals(d));   // compares values
        System.out.println(c.intValue() == d);   // one side primitive: unboxed, value comparison
    }
}
Output
true
false
true
true

This bug passes tests with small numbers and fails in production with big ones. Always compare wrapper objects with equals() (or unbox them first).

Trap 2: unboxing null#

NullTrap.java
import java.util.HashMap;
import java.util.Map;

public class NullTrap {
    public static void main(String[] args) {
        Map<String, Integer> stock = new HashMap<>();
        stock.put("pen", 12);

        Integer pencils = stock.get("pencil");          // null: key missing
        System.out.println(pencils);

        int safe = stock.getOrDefault("pencil", 0);     // avoid the null
        System.out.println(safe);

        try {
            int count = stock.get("pencil");            // unboxing null!
        } catch (NullPointerException e) {
            System.out.println("NPE while unboxing");
        }
    }
}
Output
null
0
NPE while unboxing

Whenever a wrapper might be null, check before using it as a primitive, or use defaults like getOrDefault.

Trap 3: performance in loops#

Java
Long total = 0L;                       // wrapper
for (long i = 0; i < 10_000_000; i++) {
    total += i;                        // unbox, add, box a NEW Long, every time
}

Changing Long total to long total makes this loop several times faster and avoids creating millions of objects. Use primitives for local arithmetic, and wrappers only where an object is required.

Trap 4: overloading with boxing#

List<Integer> has both remove(int index) and remove(Object o):

RemoveTrap.java
import java.util.ArrayList;
import java.util.List;

public class RemoveTrap {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(10, 20, 30, 1));
        nums.remove(1);                      // removes at INDEX 1 (the 20)
        System.out.println(nums);
        nums.remove(Integer.valueOf(1));     // removes the VALUE 1
        System.out.println(nums);
    }
}
Output
[10, 30, 1]
[10, 30]

Overload resolution prefers an exact primitive match over autoboxing, so remove(1) means index 1.

Primitive or wrapper?#

Use a primitive (int)Use a wrapper (Integer)
local variables, counters, arithmeticelements of collections, type arguments
fields that always have a valuefields where "no value" (null) is meaningful
performance-sensitive loopsvalues from nullable database columns
return values that always existwhen an API requires Object

Common mistakes#

  • Comparing Integers with ==.
  • Unboxing a possibly-null wrapper.
  • Using Integer/Long for loop counters and accumulators.
  • new Integer(5): these constructors are deprecated for removal. Use Integer.valueOf(5) or autoboxing.

What's next#

You have seen List<Integer> and Map<String, Integer>. The angle brackets are generics: type parameters that make classes and methods reusable and type-safe. That is our next lesson.

Check your understanding

Quick quiz

0/3 answered
  1. 1.Why can't you write List<int>?

  2. 2.What does this print? Integer a = 1000, b = 1000; System.out.println(a == b);

  3. 3.What happens when you run Integer count = null; int n = count;?

Finished reading?

Mark this lesson complete to track your progress.