skip to content

Reifiable vs Non-Reifiable Types

A reifiable type is fully known at runtime; List<String> is not, because erasure discarded the argument. This distinction is the reason generic arrays and parameterized instanceof are forbidden, which is exactly how interviewers use it.

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

questions

5

What does it mean for a type to be 'reifiable' in Java, and why does the concept exist?

level: middleimportance: must knowfreq 55%

answer

  1. Reify = make real at runtime
  2. Erasure throws away type arguments
  3. Reifiable: primitives, non-generic, raw, <?> unbounded, arrays of reifiable
  4. Non-reifiable: List<String>, bounded wildcards, T
  5. Gates instanceof, array creation, checked casts

basics

~20 s

A reifiable type is one whose full type information is still available at runtime. Because Java generics are erased (forgotten) at compile time, types like List<String> are NOT reifiable, while String, int, and List (the raw type) are.

solid answer

~40 s

A type is reifiable when its type information is fully present at runtime. Java implements generics by type erasure: the compiler checks generic types, then removes the type parameters so the JVM never sees them. That makes most parameterized types like List<String> non-reifiable, because at runtime there is just List. Reifiable types are those that don't lose anything to erasure: primitives (int), non-generic classes/interfaces (String, Runnable), raw types (List), parameterizations where every argument is an unbounded wildcard (List<?>, Map<?,?>), and arrays whose component type is itself reifiable (String[], List<?>[]). The concept exists because some language features only work when the type is fully known at runtime: creating arrays, instanceof checks, and casts that the JVM can actually verify. So 'reifiable' is the dividing line the spec uses to decide which generic operations are allowed.

go deeper

for a junior

Can state that generics are erased and that runtime doesn't know the <String> part; recognizes List<String> isn't 'fully real' at runtime.

for a middle

Defines reifiable precisely and lists the categories (primitive, non-generic, raw, unbounded wildcard, array of reifiable) and contrasts with non-reifiable.

for a senior

Connects reifiability to the concrete restrictions it gates (instanceof, array creation, unchecked casts) and explains why unbounded wildcards are reifiable.

for a principal

Can reason about the compatibility rationale for erasure vs reification, trade-offs versus reified generics (C#/.NET), and how this shapes API/library design decisions.

## The core problem: erasure When Java added generics in Java 5, it had to stay compatible with older bytecode and the existing JVM. The chosen mechanism is **type erasure**: the compiler uses the type arguments (`<String>`) to check your code, then *erases* them. After compilation, `List<String>` and `List<Integer>` are both simply `List` at the bytecode/runtime level. The type parameter `T` is replaced by its bound (`Object` for an unbounded `T`, or the upper bound like `Number` for `T extends Number`). So at **runtime**, the JVM has no idea a particular `List` was declared to hold `String`. That information was thrown away. ## What 'reifiable' means **Reify** means 'to make real/concrete'. A **reifiable type** is a type whose complete type information is *still present and available at runtime* — nothing was lost to erasure. Equivalently: the runtime representation of the type is exactly the same as the source-code type. A **non-reifiable type** is one that loses information to erasure, so its runtime form is less specific than what you wrote. ## The list of reifiable types (per the Java Language Specification) A type is reifiable if it is one of: 1. A **primitive** type — `int`, `double`, `boolean`, etc. (no generics involved). 2. A **non-generic** class or interface type — `String`, `Number`, `Runnable`. 3. A **raw type** — `List`, `Map`, `ArrayList` (generic class used with no type arguments at all). 4. A parameterized type where **all** type arguments are **unbounded wildcards** — `List<?>`, `Map<?,?>`, `Collection<?>`. There is nothing specific to lose: `?` already means 'some unknown type', which is what runtime knows anyway. 5. An **array** whose component type is reifiable — `int[]`, `String[]`, `List<?>[]`, `Number[][]`. ## The list of non-reifiable types Everything else with 'real' type arguments: - `List<String>`, `ArrayList<Integer>`, `Map<String,User>` — concrete arguments are erased. - `List<? extends Number>`, `List<? super Integer>` — *bounded* wildcards still carry information that is lost. - A bare type variable like `T` (it's not a concrete type either). ## Why this distinction matters The JLS uses 'reifiable' as the gate for operations that need real runtime type info: - **`instanceof`** can only test against reifiable types: `x instanceof List<?>` is legal, `x instanceof List<String>` is a compile error — at runtime there's no way to check the `String`. - **Array creation** with `new` requires a reifiable component type: `new List<String>[10]` is illegal (generic array creation error); `new List<?>[10]` is allowed. - **Casts** to non-reifiable types compile only with an 'unchecked cast' warning, because the JVM can't fully verify them. ## Mental model Think of erasure as the compiler taking off the generic 'labels' before handing the object to the JVM. Reifiable types are the ones with no labels to lose (or whose only label is the catch-all `?`). Everything that had a meaningful label removed is non-reifiable, and the language forbids exactly those operations that would need the missing label.

  • Is List<?> reifiable? Why or why not?
    Yes. An unbounded wildcard means 'some unknown type argument', which is exactly all the runtime knows after erasure — nothing specific is lost, so List<?> is reifiable.
  • What does a type variable like T erase to?
    To its leftmost bound: Object for an unbounded T, or the upper bound (e.g. Number) for T extends Number. T itself is not reifiable.

saying these in an interview costs you the question

  • Saying List<String> is reifiable because you can see <String> in the source — runtime is what matters
  • Claiming List<?> is non-reifiable (it IS reifiable — unbounded wildcard loses nothing)
  • Confusing erasure with reflection: getClass() on a List<String> returns List.class, proving erasure
  • Thinking primitives are 'not generic so the question doesn't apply' — they are explicitly reifiable

context

open as a page

What instanceof and cast operations are restricted by reifiability, and why?

level: middleimportance: should knowfreq 45%

basics

~20 s

You can only use instanceof with reifiable types. 'x instanceof List<String>' is a compile error because the <String> is erased and can't be checked at runtime; use 'x instanceof List<?>' or raw 'List' instead. Casts to List<String> compile but give an 'unchecked' warning.

open as a page

Given a mix of types (int, String, List, List<?>, List<String>, List<? extends Number>, String[], List<String>[]), classify each as reifiable or non-reifiable and justify.

level: seniorimportance: should knowfreq 35%

basics

~20 s

Reifiable: int, String, List (raw), List<?>, String[]. Non-reifiable: List<String>, List<? extends Number>, List<String>[]. The rule: a type is reifiable if it loses nothing to erasure — primitives, non-generic types, raw types, unbounded-wildcard parameterizations, and arrays of reifiable types.

open as a page

Why does Java forbid creating arrays of a parameterized type like new List<String>[10], and how do you work around it?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Arrays remember their element type at runtime and check every store against it. A List<String>[] can't do that check (the <String> is erased), so Java bans new List<String>[10] to keep the array's store-check honest. Workarounds: use new List<?>[], or better, use a List<List<String>>.

open as a page

Why did Java implement generics via erasure rather than reification, and what are the trade-offs versus a reified system like C#'s?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Java chose erasure mainly for backward compatibility: generic code had to run on the existing JVM and interoperate with pre-generics libraries without rewriting them. The cost is that type info is lost at runtime, causing the reifiability restrictions (no new T[], limited instanceof). C# reified generics, keeping runtime type info, at the price of breaking with its earlier model.

open as a page