skip to content

Type Erasure

Generics exist only at compile time: erasure removes them, bridge methods patch up polymorphism, raw types persist for compatibility, and heap pollution is the cost. Almost every hard generics question traces back to erasure.

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

explore

questions

29

What is type erasure in Java generics, and what does the compiler do to the type parameters?

level: juniorimportance: must knowfreq 78%

answer

  1. Compiler checks types, then deletes them
  2. Unbounded → Object, bounded → leftmost bound
  3. Raw type left at runtime (List, not List<String>)
  4. Synthetic casts inserted at read sites
  5. Backward compatibility + no runtime cost

basics

~20 s

Type erasure means the generic type information (like the String in List<String>) is removed during compilation. At runtime, a List<String> and a List<Integer> are both just List. The compiler uses the types only for checking and then drops them.

solid answer

~40 s

Type erasure is how Java implements generics without changing the bytecode/JVM. During compilation the compiler checks all the type arguments for safety, then erases them: every type parameter is replaced in the class and method signatures. An unbounded parameter (like T in List<T>) becomes Object; a bounded one (T extends Number) becomes its leftmost bound (Number). Because the information is gone at runtime, generics are essentially a compile-time-only feature with no runtime overhead and no extra classes generated per type argument. To keep things type-safe despite the erasure, the compiler inserts synthetic casts at the points where you read generic values out. The main consequence is that generic type arguments are not reified, so you cannot ask at runtime what T was.

go deeper

for a junior

Can state that generics are a compile-time feature and the <String> is removed after compilation, so List<String> is just List at runtime.

for a middle

Knows the concrete rules: unbounded→Object, bounded→leftmost bound, raw type remains, compiler inserts casts — and why (backward compatibility, no JVM change).

for a senior

Explains the non-reified consequence chain (no new T()/T.class/instanceof List<String>/overload-by-argument) and contrasts with reified generics in C#.

for a principal

Discusses bridge methods, the Signature attribute retaining generic metadata, the migration-compatibility rationale, and trade-offs vs. reification/specialization (Valhalla).

## The problem generics solve Before Java 5 (2004), collections held `Object`. You wrote `List list = ...; String s = (String) list.get(0);` — manual casts everywhere, and a wrong cast blew up at runtime with `ClassCastException`. **Generics** let you write `List<String>` so the compiler verifies element types for you and inserts the casts automatically. ## What "type erasure" means Java had to add generics **without breaking the millions of existing class files** and **without changing the JVM**. The chosen technique is *type erasure*: the type arguments live only in the compiler's view of the program (the source and a little metadata), and are **erased** — removed — from the actual runtime types. Define the key terms: - **Type parameter**: the placeholder you declare, e.g. the `T` in `class Box<T>` or the `E` in `interface List<E>`. - **Type argument**: the concrete type you plug in, e.g. the `String` in `Box<String>`. - **Erasure**: the mapping the compiler applies to remove parameters from signatures. ## The erasure rules (the mechanism) 1. **Unbounded type parameter erases to `Object`.** In `class Box<T> { T value; }`, after erasure the field is of type `Object`, and `T get()` becomes `Object get()`. 2. **Bounded type parameter erases to its leftmost bound.** In `class Box<T extends Number> { T value; }`, erasure replaces `T` with `Number`. With multiple bounds `T extends Number & Comparable<T>`, it erases to the **first/leftmost** bound (`Number`); the leftmost is normally chosen to be a class for this reason. 3. **Parameterized types lose their arguments.** `List<String>` and `List<Integer>` both erase to the **raw type** `List`. `List<String>[]` erases to `List[]`. 4. **The compiler inserts casts.** Because `Box.get()` now returns `Object` at the bytecode level, every call site `String s = box.get();` gets a synthetic `(String)` cast injected by the compiler. You never wrote it, but it is in the bytecode, preserving type safety. 5. **Bridge methods** may be generated to make erasure work with polymorphism/overriding, but that is a deeper detail. ## The big consequence: generics are not reified *Reified* means "the type information exists at runtime." In Java generics are **non-reified** — erased. Therefore: - `new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass()` is `true` (both are `ArrayList.class`). - You **cannot** write `new T()`, `T.class`, `new T[10]`, or `obj instanceof List<String>` — the type isn't there at runtime. - You cannot overload two methods whose signatures differ only by type argument (`void f(List<String>)` and `void f(List<Integer>)`) — after erasure they are the same method. ## Why this is the trade-off Erasure buys **backward compatibility** (old code interoperates with generic code via raw types) and **zero runtime cost / no class bloat** (one `ArrayList` class serves all arguments, unlike C++ templates or C# reified generics which generate specialized code). The price is the lost runtime type information, which forces workarounds like passing `Class<T>` tokens (`Class.cast`), the `@SuppressWarnings("unchecked")` you sometimes need, and the inability to reflectively recover `T`.

  • If erasure removes the type, why don't you get a ClassCastException when reading from a List<String>?
    Because the compiler already proved the list only ever received Strings, and it inserts the matching cast at the read site. The cast can only fail if you defeated the compiler (e.g. via a raw type or unchecked cast).
  • Does erasure mean there is zero generic type info in the class file?
    Not quite — signatures of fields, methods, and supertypes retain generic info as metadata (the Signature attribute) for compilation against the class, but the executable types/instances are erased, so it isn't available for runtime decisions on an object.

saying these in an interview costs you the question

  • Saying List<String> and List<Integer> are different classes at runtime
  • Believing the type argument can be recovered at runtime via reflection on the object
  • Confusing erasure with C++ template instantiation (no per-type code is generated)
  • Thinking erasure causes runtime overhead — it's the opposite

context

open as a page

What is a raw type in Java, and why does the language even allow you to write `List` without type arguments?

level: juniorimportance: must knowfreq 70%

basics

~10 s

A raw type is a generic class or interface used without its type arguments, like writing List instead of List<String>. Java allows it mainly so old pre-generics code (before Java 5) still compiles.

open as a page

What is type erasure in Java generics, and what happens to generic type information when code is compiled to bytecode?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Type erasure means the compiler removes generic type arguments after checking them, so at runtime a List<String> is just a List. The type parameters are replaced with their bounds (usually Object), and casts are inserted automatically.

open as a page

What concrete operations does type erasure forbid or complicate, and what are the standard workarounds?

level: middleimportance: must knowfreq 70%

basics

~20 s

Because generic types are gone at runtime you cannot do new T[], T.class, or instanceof List<String>, and you cannot overload methods that differ only by type argument. Workarounds: pass a Class<T> token, use reflective array creation, or use a super type token.

open as a page

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

level: middleimportance: must knowfreq 55%

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.

open as a page

What operations become impossible because of type erasure, and how do you work around them?

level: seniorimportance: must knowfreq 66%

basics

~20 s

Because the type is gone at runtime, you can't do new T(), T.class, new T[10], or check obj instanceof List<String>. You also can't overload methods that differ only by their generic argument. The common fix is to pass a Class<T> token so the type is available at runtime.

open as a page

If generics are erased, how is it that reflection can still recover declared type arguments at runtime? What role does the class-file Signature attribute play?

level: seniorimportance: must knowfreq 62%

basics

~20 s

Erasure only changes the object's runtime type. The compiler still records the full generic form of declarations (superclass, fields, method params/returns) in a hidden class-file 'Signature' attribute. Reflection reads that attribute to report the original type arguments.

open as a page

How do raw types cause heap pollution, and why does the compiler allow code that mixes raw and parameterized types?

level: juniorimportance: should knowfreq 30%

basics

~20 s

A raw type like plain List turns off generic checking, so you can put any object into it. If that same object is also viewed as a List<String>, you have heap pollution. Java allows the mix only so old pre-generics code still compiles.

open as a page

What is a bridge method in Java, and why does the compiler generate one when a generic supertype is overridden?

level: middleimportance: should knowfreq 35%

basics

~20 s

A bridge method is an extra method the compiler adds automatically when you override a method from a generic class or interface. It exists so that overriding still works after generics are erased to raw types at compile time.

open as a page

What is heap pollution in Java generics, and how does it lead to a ClassCastException far from its cause?

level: middleimportance: should knowfreq 35%

basics

~20 s

Heap pollution is when a variable typed for one kind of element (say List<String>) actually points to an object holding the wrong type. The compiler trusted you, so the error only shows up later as a ClassCastException when something reads the value.

open as a page

How does the compiler erase a bounded type parameter, and why is the order of bounds significant?

level: middleimportance: should knowfreq 52%

basics

~20 s

A type parameter with a bound, like T extends Number, is replaced by that bound (Number) instead of Object. With several bounds (T extends A & B), the compiler uses the first one listed, so the order you write the bounds matters.

open as a page

Where and why does the compiler insert casts to keep erased generic code type-safe?

level: middleimportance: should knowfreq 48%

basics

~20 s

Because erasure turns a generic method's return type into Object (or the bound), the compiler automatically adds a cast wherever you read a generic value, like turning list.get(0) into (String) list.get(0). You don't see these casts, but they're in the bytecode and keep your code type-safe.

open as a page

What is an unchecked warning, when does using a raw type trigger one, and how should you respond to it?

level: middleimportance: should knowfreq 58%

basics

~20 s

An unchecked warning is the compiler telling you it can't guarantee an operation is type-safe — typically because you used a raw type. The fix is to parameterize the type properly, not to ignore the warning.

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

What problems can bridge methods cause when you process methods via reflection, and how do you handle them?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Reflection lists bridge methods alongside real ones, so you can see two methods with the same name. You should skip bridge methods (check Method.isBridge()) so you don't process duplicates or pick the one with the wrong, erased parameter/return types.

open as a page

When is @SafeVarargs legitimately applicable, and what does it actually do?

level: seniorimportance: should knowfreq 45%

basics

~20 s

@SafeVarargs tells the compiler 'I checked this generic varargs method, it does not misuse the array, so stop warning.' Use it only when the method just reads the varargs and never lets the array escape or get the wrong type stored in it.

open as a page

Why does a generic varargs method produce a heap-pollution warning at compile time?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Varargs are really an array under the hood. With generics you would need an array of a generic type, which Java cannot fully create safely. So the compiler warns that this array could be misused and cause type errors later.

open as a page

Explain the rules for assigning between parameterized types and raw types in both directions, and why they differ.

level: seniorimportance: should knowfreq 48%

basics

~20 s

You can assign a parameterized type (like List<String>) to a raw type (List) freely — no warning. Going the other way, raw List to List<String>, compiles but gives an unchecked warning because the compiler can't verify the element type.

open as a page

Show how a raw type can compile cleanly (apart from a warning) yet cause a ClassCastException at runtime, and explain exactly where and why the exception is thrown.

level: seniorimportance: should knowfreq 44%

basics

~20 s

If you add a wrong-typed object through a raw List, it compiles (with a warning). The crash happens later, when other code reads an element expecting a specific type — the compiler-inserted cast fails and throws ClassCastException.

open as a page

Explain the 'super type token' (TypeReference) idiom: how does subclassing a generic type let you capture a full generic type at runtime despite erasure?

level: seniorimportance: should knowfreq 48%

basics

~20 s

You make an anonymous subclass of a generic base, like new TypeReference<List<String>>(){}. Because a subclass's superclass is a declaration, its generic argument is stored in the Signature attribute. The base reads it via getClass().getGenericSuperclass() and recovers List<String>.

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

Erased vs reified generics: what are the trade-offs of Java's erasure design versus reified generics like C#, and how do projects like Valhalla relate?

level: principalimportance: should knowfreq 30%

basics

~20 s

Erased generics (Java) drop type arguments at runtime, giving backward compatibility and one class per generic type but losing runtime type info. Reified generics (C#) keep the arguments at runtime, enabling new T[] and typeof(T), at the cost of more runtime machinery and a clean break from old code.

open as a page

How do bridge methods interact with covariant return types when overriding a method?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Covariant returns let an override return a more specific type than the parent method. The JVM can't naturally dispatch on return type, so the compiler adds a bridge method with the parent's return type that calls your override and returns its result.

open as a page

Bridge methods are a symptom of how Java implemented generics. What is the underlying design tradeoff, and how would generics without bridge methods look?

level: principalimportance: nice to knowfreq 12%

basics

~20 s

Java added generics using type erasure to stay compatible with older code that had no generics, so type info is dropped at compile time. Bridge methods exist to patch up overriding after erasure. With reified generics (like C#), types are kept at runtime and no bridges are needed.

open as a page

How would you design a generic varargs API so that heap pollution is impossible rather than merely asserted away?

level: principalimportance: nice to knowfreq 22%

basics

~10 s

Don't rely on @SafeVarargs promises. Inside the method, immediately copy the varargs into a List and work with that, never returning or storing the raw array. Then there is nothing that can be polluted.

open as a page

Why did Java choose erasure for generics rather than reified generics, and what are the trade-offs?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

Java used erasure so that new generic code could work with old pre-Java-5 libraries and run on the unchanged JVM, with no extra classes generated. The cost is losing the type at runtime, unlike C#, which kept full type info (reified generics).

open as a page

When, if ever, is using a raw type actually acceptable or required in modern Java, rather than a parameterized type or wildcard?

level: principalimportance: nice to knowfreq 30%

basics

~10 s

Almost never in new code. The legitimate cases are narrow: using class literals (List.class) and the instanceof operator, where you must use the raw form. Otherwise prefer List<String> or the wildcard List<?>.

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