Why does Java reject `obj instanceof List<String>` at compile time, and what is the only generic form of `instanceof` it does allow?
answer
- Erasure: <String> gone at runtime
- Only List<?> or raw List allowed
- instanceof T also illegal (T erased)
- Check an element, not the container
- Reifiable vs non-reifiable
basics
~20 sJava 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 sGenerics 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// 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
Knows that instanceof List<String> is a compile error and that you can only write List<?> or raw List.
Explains it via type erasure: the type argument is gone at runtime so the check is unanswerable, and names the unbounded-wildcard exception.
Connects it to reifiability, contrasts with bounded wildcards and instanceof T, and shows the element-inspection / Class<T> token workarounds.
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()