skip to content

No instanceof with Parameterized Types

instanceof needs a reifiable type, so List<String> and a bare T are rejected while List<?> is allowed. Interviewers use it as the most visible everyday consequence of erasure.

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

questions

5

Why does Java reject `obj instanceof List<String>` at compile time, and what is the only generic form of `instanceof` it does allow?

level: middleimportance: must knowfreq 62%

answer

  1. Erasure: <String> gone at runtime
  2. Only List<?> or raw List allowed
  3. instanceof T also illegal (T erased)
  4. Check an element, not the container
  5. Reifiable vs non-reifiable

basics

~20 s

Java erases generic type info at compile time, so at runtime there is no String type stored inside the list to check against. You can only write obj instanceof List<?> (the unbounded wildcard) or obj instanceof List (raw).

solid answer

~40 s

Generics in Java are implemented by type erasure: `List<String>` and `List<Integer>` both become plain `List` at runtime, with the type argument discarded. `instanceof` is a runtime test, but at runtime there is no `String`-ness recorded inside the object, so the JVM literally could not answer `instanceof List<String>` — the compiler forbids it rather than silently checking only `List`. The one allowed generic form is the unbounded wildcard, `instanceof List<?>`, because `<?>` carries no type argument to verify, so it is reifiable and equivalent to checking the raw `List`. A raw `instanceof List` is also legal but raises a rawtypes warning. The same rule blocks `instanceof T` for a type parameter, since `T` is erased too.

code

java · 10 lines
java
// Will NOT compile: illegal generic type for instanceof
// if (obj instanceof List<String>) { ... }

// Allowed: unbounded wildcard (reifiable)
if (obj instanceof List<?> list) {
    // To check element type, inspect a real element:
    if (!list.isEmpty() && list.get(0) instanceof String) {
        // ...
    }
}

go deeper

for a junior

Knows that instanceof List<String> is a compile error and that you can only write List<?> or raw List.

for a middle

Explains it via type erasure: the type argument is gone at runtime so the check is unanswerable, and names the unbounded-wildcard exception.

for a senior

Connects it to reifiability, contrasts with bounded wildcards and instanceof T, and shows the element-inspection / Class<T> token workarounds.

for a principal

Frames it as a deliberate language design tradeoff (erasure for backward compatibility) and discusses migration-compatibility implications and how heap pollution / unchecked warnings stem from the same root.

## The terms first **Generics** let you parameterize a type by another type — `List<String>` is "a list whose elements are Strings". The thing in angle brackets (`String`) is the **type argument**; the placeholder (`E` in `List<E>`) is the **type parameter**. **`instanceof`** is a runtime operator: `x instanceof Foo` asks the JVM, *at the moment the program runs*, "is the object referenced by `x` actually a Foo (or a subtype)?" It returns a boolean and is used before a cast. **Type erasure** is the key mechanism. Java added generics in Java 5 *without* changing the bytecode/JVM, to stay backward compatible with pre-generics code. The compiler uses the type arguments to check your code, then **erases** them: `List<String>`, `List<Integer>`, and `List<?>` all compile down to the same runtime type — the raw `List`. The `<String>` part exists only in the source and (partly) in metadata; it is **not** stored in each object instance. **Reifiable type** = a type whose full information *survives* to runtime so it can be checked or constructed. `String`, `int[]`, `List` (raw), and `List<?>` are reifiable. `List<String>` is **not reifiable** — its type argument is gone after erasure. ## Why `instanceof List<String>` is illegal Because the `<String>` was erased, *every* `List` object at runtime looks identical regardless of its element type. There is no field, header bit, or class tag that records "this list holds Strings." So a runtime `instanceof List<String>` check is **unanswerable** — the JVM could only ever check the erased `List`, which would make the `<String>` a lie. Rather than silently degrade the check (and give you a false sense of safety), the Java language **rejects it at compile time**: *"illegal generic type for instanceof"*. ```java List<String> a = new ArrayList<>(); List<Integer> b = new ArrayList<>(); // a.getClass() == b.getClass() is TRUE at runtime — both are ArrayList ``` ## What IS allowed 1. **Unbounded wildcard:** `obj instanceof List<?>`. The `<?>` means "a list of *some* unknown type" — it carries **no** type argument to verify, so the check reduces exactly to the reifiable raw `List`. This is the idiomatic, warning-free form. 2. **Raw type:** `obj instanceof List`. Legal but produces an unchecked/rawtypes warning; prefer `List<?>`. 3. **Bounded wildcards are NOT allowed:** `instanceof List<? extends Number>` is rejected — the bound is non-reifiable info too. 4. **Type parameter is NOT allowed:** inside `class Box<T>`, you cannot write `x instanceof T` — `T` is erased to its bound (often `Object`), so the check is meaningless. (Same reason `new T()` and `new T[]` are illegal.) ## How to actually test the element type Since you can't ask the list, you must inspect an **element**, which is a real reified object: ```java if (obj instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof String) { ... } ``` Or pass a `Class<T>` **type token** and use `Class.isInstance` / `Class.cast` to do reflective, reifiable checks. This pattern (a `Class<T>` parameter standing in for the erased `T`) is how libraries recover the type they need. ## Mental model Generics are a **compile-time** contract; `instanceof` is a **runtime** question. The two only meet where the type information is reifiable — which, for a parameterized type, is just its unbounded-wildcard / raw form.

  • How can you check at runtime whether a list contains Strings, given the restriction?
    You can't ask the list itself; check an element after a List<?> test: `list instanceof List<?> l && !l.isEmpty() && l.get(0) instanceof String`. Or carry a `Class<T>` type token and use `Class.isInstance`. An empty list is genuinely indistinguishable.
  • Why is `instanceof List<?>` allowed but `instanceof List<? extends Number>` not?
    `<?>` adds no type argument to verify — it collapses to the reifiable raw `List`. A bounded wildcard `<? extends Number>` does carry non-reifiable bound information that erasure removed, so it cannot be checked at runtime and is rejected.

saying these in an interview costs you the question

  • Claiming instanceof List<String> compiles but 'just checks List' — it does not compile at all
  • Saying generics are reified in Java like in C# / C++ templates
  • Thinking instanceof List<? extends Number> is allowed (bounded wildcards are rejected)
  • Believing the type argument is stored per-instance and is retrievable via getClass()

context

open as a page

Given the instanceof restriction, how would you determine the element type of a `List` at runtime, and what are the limits of any approach?

level: middleimportance: should knowfreq 30%

basics

~20 s

You can't ask the list its element type — that was erased. Test it with instanceof List<?>, then look at an actual element (list.get(0) instanceof String). This fails for an empty list, where the element type is simply unknowable at runtime.

open as a page

What does it mean for a type to be 'reifiable' in Java, and which generic types are reifiable?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A reifiable type is one whose full type information is still available at runtime. Plain types, raw types, and unbounded wildcards like List<?> are reifiable; parameterized types like List<String> are not, because erasure throws the type argument away.

open as a page

Inside a generic class `Box<T>`, why can't you write `x instanceof T` (or `new T()`), and what is the standard workaround?

level: seniorimportance: should knowfreq 38%

basics

~20 s

At runtime T is erased to Object (or its bound), so there is no real T to test against — x instanceof T and new T() are both illegal. The fix is to pass a Class<T> object and use clazz.isInstance(x) and clazz.getDeclaredConstructor().newInstance().

open as a page

How does Java's 'no instanceof with parameterized types' restriction compare to C#, where `obj is List<string>` works — and why did Java make this design choice?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

C# reifies generics: the runtime keeps the type argument, so obj is List<string> works. Java erases generics, so the type argument is gone at runtime and the check is impossible. Java chose erasure to stay backward-compatible with pre-generics code and bytecode.

open as a page