Wrapper Classes & Autoboxing

Reviewed & published by Brayan K

Java's eight primitives are fast but they aren't objects — so they can't go in a List or be null. Wrapper classes like Integer wrap a primitive in an object, and autoboxing converts between the two automatically. Convenient — and full of subtle traps.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You'll need the primitive types (int, double, boolean, char...) and a first look at collections like ArrayList, which only hold objects — the main reason wrappers exist.

What You'll Learn

The Big Idea — A Gift Box for a Number

💡 Analogy: A primitive int is a bare coin sitting on the table — quick to grab, but it can't be labelled or shipped. An Integer is that same coin sealed in a small gift box: now it has a label, you can put it on a shelf alongside other boxes (a collection), and the box can be empty (null). Autoboxing is the machine that drops the coin in the box; unboxing takes it back out.

Every primitive has a matching wrapper: int→Integer, double→Double, boolean→Boolean, char→Character, long→Long, short→Short, byte→Byte, float→Float. They live in java.lang, so no import is needed.

1️⃣ The Eight Wrapper Classes

Each primitive maps to exactly one wrapper class. Most names just capitalise the primitive; the two exceptions are int→Integer and char→Character.

PrimitiveWrapperNote
intIntegerName differs from the primitive
charCharacterName differs from the primitive
doubleDoubleJust capitalised
booleanBooleanJust capitalised
longLongJust capitalised
shortShortJust capitalised
byteByteJust capitalised
floatFloatJust capitalised
public class Main {
    public static void main(String[] args) {
        // A primitive and its wrapper object
        int primitive = 42;
        Integer wrapped = Integer.valueOf(42);   // explicit boxing

        System.out.println("primitive int : " + primitive);
        System.out.println("Integer object: " + wrapped);

        // Each primitive has a matching wrapper class:
        Integer   i = 100;
        Double    d = 3.14;
        Boolean   b = true;
        Character c = 'A';
        Long      l = 9000000000L;

        System.out.println("Integer  : " + i);
        System.out.println("Double   : " + d);
        System.out.println("Boolean  : " + b);
        System.out.println("Character: " + c);
        System.out.println("Long     : " + l);
    }
}

2️⃣ Autoboxing & Auto-Unboxing

Since Java 5, the compiler converts between a primitive and its wrapper automatically wherever the context demands it. Autoboxing wraps a primitive into an object; auto-unboxing pulls the primitive back out.

Integer boxed = 5;     // autoboxing:   Integer.valueOf(5)
int back = boxed;      // auto-unboxing: boxed.intValue()

This is why you can write list.add(90) even though a List<Integer> only stores objects — the 90 is boxed for you. The convenience is real, but remember it's real method calls under the hood.

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

public class Main {
    public static void main(String[] args) {
        // AUTOBOXING: int -> Integer happens automatically
        Integer boxed = 5;          // really Integer.valueOf(5)

        // AUTO-UNBOXING: Integer -> int happens automatically
        int unboxed = boxed;        // really boxed.intValue()
        System.out.println("boxed=" + boxed + ", unboxed=" + unboxed);

        // Collections can only hold OBJECTS, so primitives are auto-boxed:
        List<Integer> scores = new ArrayList<>();
        scores.add(90);             // int 90 -> Integer.valueOf(90)
        scores.add(75);
        scores.add(88);

        int total = 0;
        for (int s : scores) {      // each Integer auto-unboxes to int
            total += s;
        }
        double average = (double) total / scores.size();
        System.out.println("Total: " + total);
        System.out.printf("Average: %.1f%n", average);
    }
}

3️⃣ The == Trap (Use .equals!)

This is the most infamous wrapper gotcha. For memory efficiency, Java caches Integer objects for the range -128 to 127. Two boxed values in that range share one cached object, so == (which compares references) returns true. Outside the range, each box is a new object and == returns false — even when the numbers are equal.

public class Main {
    public static void main(String[] args) {
        // The == trap with wrapper objects.
        // Java caches Integer values from -128 to 127, so small equal
        // values share one cached object — but larger ones do not.
        Integer a = 100;
        Integer b = 100;
        System.out.println("100 == 100  : " + (a == b));        // cached -> true

        Integer c = 200;
        Integer d = 200;
        System.out.println("200 == 200  : " + (c == d));        // not cached -> false

        // .equals() compares the VALUE, so it is always correct:
        System.out.println("200.equals  : " + c.equals(d));     // true

        // Comparing a wrapper to a primitive auto-unboxes, so == is fine there:
        int primitive = 200;
        System.out.println("c == 200    : " + (c == primitive)); // unboxes -> true
    }
}

4️⃣ The null Danger

A wrapper can hold null — useful for "no value yet". But the moment Java tries to auto-unbox a null wrapper into a primitive, it calls a method on null and throws a NullPointerException. This is one of the sneakiest NPEs because the unboxing is invisible in the source.

public class Main {
    public static void main(String[] args) {
        // A wrapper can be null — a primitive cannot. That extra "null" is
        // useful for "no value yet", but unboxing a null EXPLODES.
        Integer maybe = null;
        System.out.println("maybe is null? " + (maybe == null));

        try {
            int x = maybe;          // auto-unboxing null -> NullPointerException
            System.out.println(x);
        } catch (NullPointerException e) {
            System.out.println("Boom! Unboxing null threw a NullPointerException");
        }
    }
}

5️⃣ Free Utilities on Wrapper Classes

Wrapper classes aren't just boxes — they're toolboxes of static helpers you'll use constantly: parse text into numbers, read type limits, and convert between number bases.

public class Main {
    public static void main(String[] args) {
        // Wrapper classes carry handy STATIC utilities.

        // Parsing text into numbers:
        int n = Integer.parseInt("256");
        double price = Double.parseDouble("19.99");
        System.out.println("parsed int   : " + n);
        System.out.println("parsed double: " + price);

        // Min / max constants:
        System.out.println("Integer.MAX_VALUE: " + Integer.MAX_VALUE);
        System.out.println("Integer.MIN_VALUE: " + Integer.MIN_VALUE);

        // Base conversions:
        System.out.println("255 in binary: " + Integer.toBinaryString(255));
        System.out.println("255 in hex   : " + Integer.toHexString(255));

        // Comparing and bounds:
        System.out.println("max(7, 3)    : " + Integer.max(7, 3));
        System.out.println("compare(5,9) : " + Integer.compare(5, 9));
    }
}

🧩 Reorder Challenge

These lines read three String prices, parse them, and print the total. They've been shuffled — find the right order.

A)     total += Double.parseDouble(p);
B) double total = 0;
C) String[] prices = {"4.50", "3.25", "2.00"};
D) System.out.printf("Total: %.2f%n", total);
E) for (String p : prices) {
F)     }

Correct order: C, B, E, A, F, D

String[] prices = {"4.50", "3.25", "2.00"};
double total = 0;
for (String p : prices) {
    total += Double.parseDouble(p);
}
System.out.printf("Total: %.2f%n", total);
// Output:  Total: 9.75

Declare the data and accumulator, loop and parse each string with the wrapper's static method, close the loop, then print.

🧠 Quick Recall

Predict the output before opening each answer.

Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println((a == b) + " " + (c == d));

Answer: true false. 127 is inside the cache (-128..127) so a and b share one object. 128 is outside it, so c and d are separate objects and == compares references.

Integer count = null;
int n = count + 1;
System.out.println(n);

Answer: It throws a NullPointerException. count + 1 needs to auto-unbox count to an int, but it's null — so the invisible count.intValue() call blows up before anything is printed.

String s = "42";
int doubled = Integer.parseInt(s) * 2;
System.out.println(doubled);

Answer: 84. parseInt turns the text "42" into the int 42, which is then doubled. (If s were "4two" it would throw a NumberFormatException instead.)

🎯 Your Turn #1 — Parse and sum

Three numbers arrive as text. Parse each with a wrapper method and print the total.

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: these three lines come as String input (e.g. from a form).
        // Parse each one to a number, add them, and print the total.
        // Expected output:  Total: 60
        String aText = "10";
        String bText = "20";
        String cText = "30";

        int total = 0;   // parse and sum here

        System.out.println("Total: " + total);
    }
}

🎯 Your Turn #2 — Compare correctly

The values are 500 — outside the cache — so == gives the wrong answer. Fix the comparison to compare values.

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: x and y are Integer objects holding the value 500.
        // 500 is OUTSIDE the cache range, so == will be false.
        // Print "equal" if their VALUES match, otherwise "different".
        // Expected output:  equal
        Integer x = 500;
        Integer y = 500;

        // replace this line with a correct value comparison:
        String result = (x == y) ? "equal" : "different";

        System.out.println(result);
    }
}

🧩 Mini-Challenge — Nullable max

Return type Integer lets you return null for an empty list — something an int can't do. Build it from the outline.

import java.util.List;

public class Main {
    // GOAL: write findMax that returns the largest value, or null for an
    // empty list. Returning Integer (not int) lets you return null.
    //
    // Expected output:
    //   Max of [3, 9, 1, 7]: 9
    //   Max of empty list  : null

    static Integer findMax(List<Integer> numbers) {
        // 1. if numbers.isEmpty() return null
        // 2. otherwise loop and track the largest (auto-unboxing as you compare)
        // 3. return the result
        return null;   // your code here
    }

    public static void main(String[] args) {
        System.out.println("Max of [3, 9, 1, 7]: " + findMax(List.of(3, 9, 1, 7)));
        System.out.println("Max of empty list  : " + findMax(List.of()));
    }
}

Common Errors

Pro Tips

📋 Quick Reference

TaskSyntaxNotes
Box a primitiveInteger i = 5;Autoboxing — Integer.valueOf(5)
Unbox to primitiveint n = i;Auto-unboxing — i.intValue()
Compare valuesa.equals(b)Never == for wrappers!
Parse textInteger.parseInt("12")NumberFormatException if bad
Type boundsInteger.MAX_VALUE2147483647
Collection typeList<Integer>Wrappers only, never primitives

🎉 Lesson Complete!

You now know every primitive's wrapper class, how autoboxing and auto-unboxing work, why collections force wrappers on you, and the two big traps — the == cache surprise and the null-unboxing NPE. You can also reach for the handy static helpers like parseInt and MAX_VALUE.

Practice quiz

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

  • false
  • It throws an exception
  • true
  • 100

Answer: true. Java caches Integer values from -128 to 127, so a and b share one cached object and == returns true. This is the cache trap.

What does this print? Integer c = 200; Integer d = 200; System.out.println(c == d);

  • false
  • true
  • 200
  • It throws an exception

Answer: false. 200 is OUTSIDE the cache range (-128..127), so c and d are separate objects and == compares references, giving false. Use .equals() instead.

What is autoboxing?

  • Converting a wrapper to a primitive
  • Comparing two wrappers
  • Parsing a String into a number
  • The compiler automatically converting a primitive to its wrapper, e.g. int 5 to Integer.valueOf(5)

Answer: The compiler automatically converting a primitive to its wrapper, e.g. int 5 to Integer.valueOf(5). Autoboxing wraps a primitive into its wrapper object where an object is expected; auto-unboxing is the reverse (Integer to int via intValue()).

What happens here? Integer count = null; int n = count + 1;

  • n becomes 1
  • It throws a NullPointerException when auto-unboxing null
  • n becomes null
  • It prints 0

Answer: It throws a NullPointerException when auto-unboxing null. count + 1 must auto-unbox count to an int, calling the invisible count.intValue() on null, which throws a NullPointerException.

Why does == sometimes work and sometimes fail with Integer?

  • Values -128..127 are cached and share an object, but outside that range each box is new
  • It depends on the day
  • == never works with Integer
  • It depends on the variable names

Answer: Values -128..127 are cached and share an object, but outside that range each box is new. Cached small values share one object so == is true; outside the cache each autoboxed value is a new object so == compares references. Always use .equals().

What does Integer.toBinaryString(255) return?

  • "FF"
  • "255"
  • "11111111"
  • "0xFF"

Answer: "11111111". 255 in binary is eight 1 bits: "11111111". (Integer.toHexString(255) would give "ff".)

Why does List<Integer> work but List<int> does not?

  • int is deprecated
  • Generics require object types, so primitives must use their wrapper classes
  • Integer is faster
  • List cannot hold numbers

Answer: Generics require object types, so primitives must use their wrapper classes. Generic collections can only hold objects, not primitives, so you use List<Integer>; the int elements autobox automatically on add.

What does Integer.parseInt("256") return, and what type is it?

  • "256" as a String
  • 256 as an Integer object
  • It throws an exception
  • 256 as an int

Answer: 256 as an int. parseInt turns the text "256" into the primitive int 256. (parseInt("12a") would throw NumberFormatException.)

Why is new Integer(5) discouraged in modern Java?

  • It is too slow to type
  • It is deprecated since Java 9 and always creates a new object, defeating the cache
  • It returns null
  • It throws an exception

Answer: It is deprecated since Java 9 and always creates a new object, defeating the cache. new Integer(5) is deprecated and always allocates a fresh object. Use Integer.valueOf(5) or just 5, which can reuse the cache.

When should you prefer primitives over wrappers?

  • Never - always use wrappers
  • Only inside collections
  • By default in loops, counters, and local variables, to avoid allocation and null/equality traps
  • Only when a value may be null

Answer: By default in loops, counters, and local variables, to avoid allocation and null/equality traps. Prefer primitives by default; reach for wrappers only when an API forces it (generics/collections) or when a value genuinely needs to be nullable.

Continue this course

Frequently asked questions

What is the difference between int and Integer?

int is a primitive — it stores a raw 32-bit value directly and is fast and memory-light. Integer is an object that wraps an int; it lives on the heap, can be null, and carries useful methods and constants. Use int for plain arithmetic and local variables; use Integer when you need an object, such as inside generic collections (List<Integer>) or when a value might be absent (null).

What is autoboxing and auto-unboxing?

Autoboxing is the compiler automatically converting a primitive to its wrapper (int 5 becomes Integer.valueOf(5)) where an object is expected. Auto-unboxing is the reverse — a wrapper is converted back to a primitive (Integer to int via intValue()) where a primitive is expected. It lets you write list.add(5) instead of list.add(Integer.valueOf(5)), but it hides real method calls, so it has a small cost and can throw a NullPointerException when unboxing null.

Why does == sometimes work and sometimes fail with Integer?

Java caches Integer objects for values from -128 to 127. Two Integers with the same small value share one cached object, so == returns true. Outside that range each autoboxed value is a new object, so == compares references and returns false even when the values match. The rule: never use == to compare wrapper values — always use .equals() (or unbox to primitives first).

Can a wrapper be null and why does that matter?

Yes. Unlike a primitive, an Integer can be null, which is handy for representing 'no value yet'. The danger is auto-unboxing: if you assign a null Integer to an int, or do arithmetic on it, Java tries to call intValue() on null and throws a NullPointerException. Always null-check a wrapper before unboxing it.

When should I prefer primitives over wrappers?

Prefer primitives by default — in loops, counters, arithmetic, and local variables — because they avoid object allocation and the null/equality traps. Reach for wrappers only when the API forces it: generics and collections (which can't hold primitives), or when a value genuinely needs to be nullable. Avoid declaring fields as wrappers unless null is a meaningful state.

Related lessons