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?
answer
- C# reifies, Java erases
- Java: migration compatibility with pre-generics code
- Erasure → one runtime class per generic
- Cost: no instanceof T / new T[] / heap pollution
- Project Valhalla may add partial reification
basics
~20 sC# 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.
solid answer
~40 sC# implements generics by **reification**: the CLR creates and tracks a distinct runtime type for each closed generic like `List<string>`, so `obj is List<string>` and even `typeof(T)` work at runtime. Java implements generics by **erasure**: the type argument is checked at compile time then discarded, so all `List<…>` share one runtime class and the type test is unanswerable — hence the restriction. Java chose erasure deliberately for **migration compatibility**: generics were retrofitted onto Java 5 without changing the JVM or breaking the billions of lines of pre-generics code and libraries; a raw `List` and `List<String>` had to interoperate seamlessly. The cost is the well-known erasure limitations (no `instanceof T`, no `new T[]`, heap pollution). Project Valhalla is exploring partial reification, but classic Java generics remain erased.
go deeper
Aware that other languages (C#) can test generic types at runtime and Java cannot.
Names erasure vs reification as the core difference and that Java's choice was for compatibility.
Explains migration compatibility, lists the concrete trade-offs (instanceof, new T, boxing, heap pollution), and the C# reification consequences.
Treats it as a language-design case study: weighs ecosystem continuity vs runtime power, anticipates Valhalla's direction, and applies the reasoning when designing cross-language or long-lived APIs.
## Two philosophies of generics **Reification** (C#/.NET): the runtime *keeps* generic type arguments. The CLR JIT-instantiates a separate concrete type for each closed generic — `List<int>`, `List<string>` are genuinely different runtime types. Consequences: `obj is List<string>` works, `typeof(T)` works, `new T()` works (with a `new()` constraint), and value types avoid boxing because `List<int>` stores raw ints. **Erasure** (Java): generic type arguments are a *compile-time-only* check. After type-checking, the compiler **erases** them — `List<String>` → `List`, `T` → `Object`/bound — producing bytecode indistinguishable from pre-generics code. Consequences: a single runtime class per generic, no `instanceof List<String>`, no `instanceof T`, no `new T[]`, and possible *heap pollution* (an unchecked cast lets a `String` list secretly hold an `Integer`). ## Why Java picked erasure: migration compatibility When generics were added in **Java 5 (2004)**, there were already enormous amounts of compiled code and libraries using raw `List`, `Map`, etc. Sun's hard requirement was **migration compatibility**: existing pre-generics binaries had to keep running unchanged, and new generic code had to interoperate with old raw-typed code in *both* directions (you can pass a `List<String>` where a raw `List` is expected and vice-versa, with only a warning). The cleanest way to guarantee that — without forking the JVM or duplicating every collection class — was to make `List<String>` and raw `List` the *same* runtime type. Erasure achieves exactly that. The JVM didn't need any new instructions, and old `.class` files still linked. C# took the opposite path because .NET generics shipped in **C# 2 / .NET 2.0 (2005)** as a *runtime* feature designed in; Microsoft was willing to extend the CLR itself, so reification was feasible without the same legacy burden. ## The trade-off ledger | | Java (erasure) | C# (reification) | |---|---|---| | `obj instanceof/is List<String>` | ❌ illegal | ✅ works | | `new T()` / `typeof(T)` | ❌ (use Class<T> token) | ✅ (with constraints) | | Value types in collections | boxed (until Valhalla) | no boxing | | Backward-compat with raw types | ✅ seamless | n/a (no legacy raw types) | | Runtime/metadata cost | minimal (one class) | per-instantiation code/metadata | | Code bloat risk | none | possible (many instantiations) | Erasure also gives Java a subtle benefit: no per-instantiation code bloat and trivial interop with reflective/raw code — at the price of the developer ergonomics around `instanceof`, arrays, and type tokens. ## The future: Project Valhalla Java's **Project Valhalla** (value types / specialized generics) aims to allow generics over primitives without boxing and may bring *some* reified type information, narrowing the gap. But as of standard Java, generics are erased, and the `instanceof` restriction is a permanent consequence of the 2004 backward-compatibility decision — a textbook case of a language trading runtime power for ecosystem continuity. ## Why this matters to a principal This comparison is the canonical example of **how a backward-compatibility constraint shapes a language for decades**. Understanding it lets you reason about *why* the workarounds (type tokens, super-type tokens, `Class<T>` plumbing) are idiomatic in Java but absent in C#, and to set expectations when porting code or designing cross-language APIs.
- What concrete capabilities does reification give C# that erasure denies Java?Runtime type tests on closed generics (`obj is List<string>`), `typeof(T)` / `default(T)`, `new T()` with a constructor constraint, and unboxed value-type collections (`List<int>` stores raw ints). Java emulates the first three with `Class<T>` tokens and pays boxing for the last (pre-Valhalla).
- What is 'migration compatibility' and why did it force erasure?It's the requirement that pre-generics binaries keep running and that generic and raw code interoperate both ways. Making List<String> and raw List the same runtime type satisfied it without changing the JVM or duplicating collection classes — which erasure does by construction.
saying these in an interview costs you the question
- Claiming Java erases for performance reasons (it was backward compatibility)
- Saying C# also erases generics
- Asserting Valhalla has already shipped full reified generics in standard Java
- Confusing erasure with type inference (var) — unrelated