skip to content

No Primitive Type Arguments

Type arguments must be reference types, so List<int> is illegal and you pay the boxing cost of List<Integer>. Interviewers connect this to primitive streams and to why Valhalla is being worked on.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

Why can't you write List<int> in Java, and what must you use instead?

level: juniorimportance: must knowfreq 70%

answer

  1. Generics take reference types only
  2. int -> Integer wrapper
  3. Autoboxing bridges the gap
  4. Erasure stores elements as Object
  5. Cost: heap objects + boxing + NPE on null

basics

~10 s

Generic type arguments must be objects, and int is a primitive, not an object. So List<int> is illegal. Use the wrapper class instead: List<Integer>. Java then converts int to Integer for you automatically.

solid answer

~40 s

Java generics only accept reference types (objects) as type arguments, never primitives like int, double, or boolean. That's why List<int> won't compile. The reason is implementation: generics are erased to Object at compile time, and Object can only hold references, not raw primitive values. So you use the wrapper class, List<Integer>, and rely on autoboxing/unboxing: writing list.add(5) auto-wraps the int 5 into an Integer, and int x = list.get(0) auto-unwraps it. The trade-off is that each element is a separate heap object with pointer overhead, and boxing/unboxing in hot loops costs CPU and allocations. For performance-critical primitive collections, people reach for int[] arrays or libraries like Eclipse Collections / fastutil.

code

java · 9 lines
java
// List<int> nums = new ArrayList<>(); // does NOT compile

List<Integer> nums = new ArrayList<>();
nums.add(5);              // autobox: int -> Integer.valueOf(5)
int first = nums.get(0); // auto-unbox: Integer -> intValue()

// Watch out for null unboxing:
Integer boxed = null;
// int oops = boxed;     // throws NullPointerException at runtime

go deeper

for a junior

Knows List<int> is illegal and that you write List<Integer> instead, relying on autoboxing.

for a middle

Can explain it's because generics require reference types and that boxing has memory/CPU costs and a null-unboxing NPE risk.

for a senior

Ties the restriction to type erasure (elements stored as Object), and knows the alternatives (int[], primitive streams, fastutil/Eclipse Collections).

for a principal

Frames it as a language-implementation trade-off, can reason about allocation/cache impact at scale, and references Project Valhalla as the direction for primitive/value generics.

## The terms first **Primitive type:** Java has eight built-in value types — `byte`, `short`, `int`, `long`, `float`, `double`, `char`, `boolean`. A primitive variable holds the value *directly* (the bits of the number), not a reference to an object. It lives on the stack or inline in an object, and has no methods. **Reference type (object type):** Everything else — classes, interfaces, arrays. A variable of a reference type holds a *reference* (a pointer) to an object on the heap. **Wrapper class:** For each primitive there is a matching class that *wraps* a single primitive value as an object: `int`→`Integer`, `double`→`Double`, `boolean`→`Boolean`, `char`→`Character`, `long`→`Long`, etc. An `Integer` is a real object on the heap whose only job is to carry one `int`. **Generics:** A way to parameterize a type, e.g. `List<Integer>` is "a list of Integers". The thing in the angle brackets is the **type argument**. ## The rule **A generic type argument must be a reference type. Primitives are not allowed.** So: ```java List<int> nums; // COMPILE ERROR: unexpected type, required reference List<Integer> nums; // OK ``` This applies to *every* primitive and *every* generic position: `Map<int, String>`, `Optional<double>`, `Comparable<boolean>` are all illegal. ## Why — type erasure Java generics are implemented by **type erasure**: the generic type information is used by the compiler for type-checking, then *erased* in the bytecode. A `List<Integer>` and a `List<String>` are both just `List` at runtime, and internally the list stores its elements as `Object`. A primitive `int` is not an `Object` and cannot be stored in an `Object` slot — only a reference can. So the language simply forbids primitive type arguments rather than letting you write code that couldn't be represented. ## What you do instead — autoboxing You use the wrapper type and let **autoboxing/unboxing** bridge the gap: ```java List<Integer> nums = new ArrayList<>(); nums.add(5); // autobox: int 5 -> Integer.valueOf(5) int first = nums.get(0); // auto-unbox: Integer -> intValue() ``` *Autoboxing* is the compiler automatically inserting `Integer.valueOf(...)` where an `int` is used but an `Integer` is expected. *Unboxing* is the reverse, inserting `.intValue()`. ## The costs 1. **Memory:** each `Integer` is a separate heap object (header + the int + alignment ≈ 16 bytes) plus the reference to it, versus 4 bytes for a raw `int`. A `List<Integer>` of a million numbers uses far more memory than an `int[]`. 2. **CPU / allocation:** boxing in a tight loop creates garbage and adds indirection (you follow a pointer to read the value), hurting cache locality. In hot paths this is measurable. 3. **NPE risk:** an `Integer` can be `null`. Unboxing a `null` throws `NullPointerException` — `int x = someInteger;` blows up if `someInteger` is null. ## When it matters / alternatives For ordinary code, `List<Integer>` is fine. For large primitive collections or hot loops, use a primitive array (`int[]`), or a primitive-specialized collection library (Eclipse Collections, fastutil, Trove, HPPC) that stores `int` directly with no boxing. The JDK also offers primitive streams (`IntStream`, `LongStream`, `DoubleStream`) to avoid boxing in stream pipelines. With all this, a learner at any level can answer: *List<int> is illegal because generics require reference types (a consequence of erasure); use List<Integer> with autoboxing, and be aware of the memory/CPU/NPE costs.*

  • What happens when you call nums.add(5) on a List<Integer>?
    The compiler autoboxes the int 5 into an Integer via Integer.valueOf(5) and stores that object reference in the list.
  • Why does the language forbid List<int> rather than just supporting it?
    Because of type erasure: at runtime the list stores elements as Object, and a primitive int isn't an Object. Project Valhalla aims to add primitive/value-type generics in the future.

A generic container is a row of mailboxes that each hold an envelope (a reference). A primitive int is a loose coin — it doesn't fit in a mailbox until you put it in an envelope (the Integer wrapper).

saying these in an interview costs you the question

  • Claiming List<int> works (it does not compile)
  • Saying Integer and int are the same thing with no overhead
  • Forgetting that unboxing a null Integer throws NullPointerException

context

open as a page

What correctness bugs arise from boxing when you use wrapper types like Integer in generic collections?

level: middleimportance: must knowfreq 50%

basics

~20 s

Two big bugs. First, an Integer can be null, and unboxing a null throws NullPointerException. Second, comparing boxed numbers with == compares object identity, not value, so it can give wrong results; use equals() or compare as primitives.

open as a page

What are the performance implications of using List<Integer> instead of int[], and when does it matter?

level: middleimportance: should knowfreq 55%

basics

~20 s

Each number in a List<Integer> becomes a separate object on the heap with extra memory, plus the cost of boxing and unboxing. An int[] stores the raw numbers compactly with no boxing. It matters most for large data or tight loops.

open as a page

Java arrays support primitives (int[]) but generics do not (no List<int>). Why the asymmetry?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Arrays are a built-in language feature that knew about primitives from day one and the JVM has separate bytecode for primitive arrays. Generics were added later and use type erasure, where elements are treated as Object, which can't hold a primitive. So arrays got primitives, generics didn't.

open as a page

How would you design a high-throughput numeric component in Java to avoid the boxing forced by generics?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Avoid generic collections of wrappers. Use primitive arrays (int[], long[]) or primitive streams (IntStream) for the hot path, and reach for primitive-collection libraries like fastutil or Eclipse Collections when you need growable maps/lists of primitives without boxing.

open as a page