Type Erasure in Java Generics
Understand how Java generics disappear at compile time, what erasure does to your types, and the implications for your code.
Type Erasure in Java Generics
Type erasure is the mechanism by which Java implements generics. At compile time, all generic type information (<T>, <String>) is removed — replaced by their erased types (typically Object or the leftmost bound). The JVM has no knowledge of generics at runtime. This was a deliberate design choice made in Java 1.5 to maintain binary compatibility with pre-generics code.
What Gets Erased
| Generic Declaration | Erasure |
| --------------------------- | -------------------------------------------------- |
| <T> | Object |
| <T extends Number> | Number |
| <T extends Comparable<T>> | Comparable (the bound, after its own erasure) |
| <K, V> (multiple) | Each erased independently per its own bound |
| List<String> | List (raw type) |
| List<? extends Number> | List (raw type, with wildcard flag for compiler) |
After erasure, the compiled bytecode is identical to pre-generics Java code that used Object casts throughout.
Code Example: Before and After Erasure
// SOURCE CODE
public class Container<T> {
private T value;
public T get() { return value; }
public void set(T value) { this.value = value; }
}
// COMPILED BYTECODE (erasure applied)
public class Container {
private Object value; // T replaced with Object
public Object get() { return value; }
public void set(Object value) { this.value = value; }
}
The same happens at call sites:
// SOURCE
Container<String> box = new Container<>();
box.set("Hello");
// COMPILED
Container box = new Container();
box.set("Hello"); // "Hello" is already an Object — no cast needed
// retrieval: String s = box.get();
// compiler inserts: String s = (String) box.get();
// Erasure + cast insertion = what the compiler emits
Mermaid Diagram: Erasure Process — Compile Time
flowchart TD
A["public class Box<T>\n{\n T value;\n T get() { return value; }\n}"] --> B["Type Checking"]
B --> C["Erasure"]
C --> D["public class Box\n{\n Object value;\n Object get() { return value; }\n}"]
D --> E["Insert Casts"]
E --> F["Bytecode: Box.java"]
Runtime
flowchart TD
G["Loading Box.class"]
G --> H["No generic signature retained\n— just raw Box class"]
Code Example: Bounded Type Erasure
// SOURCE
public class NumericBox<T extends Number> {
private T value;
public double compute() {
return value.doubleValue(); // calling Number's method
}
}
// ERASED
public class NumericBox {
private Number value; // T replaced with leftmost bound: Number
public double compute() {
return value.doubleValue(); // still valid — Number has doubleValue()
}
}
The bound Number is kept after erasure because it is the type that provides the methods you call.
Failure Scenarios
1. Cannot Instantiate T
public <T> void createInstance(Class<T> clazz) {
// T t = new T(); // compile error: cannot do new T
// Erasure makes T = Object at runtime, so new Object() would be wrong type
T instance = clazz.getDeclaredConstructor().newInstance(); // workaround
}
2. Cannot Create Generic Arrays Directly
public <T> void wrongArray() {
// T[] arr = new T[10]; // compile error
// Fix: use Array.newInstance()
T[] arr = (T[]) Array.newInstance(Object.class, 10); // unchecked cast
}
3. Collision After Erasure
// Two methods with the same erasure — compile error
public class Colliding {
// public void set(T value) { }
// public void set(List<T> list) { } — both erase to set(Object)
}
4. Cannot Call Class methods on T at Runtime
public <T> void example(T param) {
// Class<?> c = param.getClass(); // works — getClass() is on Object
// But Class<T> methods that need T at runtime are unsafe
// T.class cannot be used: Class<T> does not give you .class at runtime
}
Trade-Off Table
| Concern | Impact of Erasure | Mitigation |
| -------------------------- | ------------------------------------------ | ----------------------------------------------------- |
| instanceof with generics | Illegal — no runtime type info | Use alternative: markers, separate methods |
| new T() | Not possible | Use Class<T>.newInstance() or Array.newInstance() |
| T.class | Not valid syntax | Pass Class<T> as parameter |
| Array creation | No direct new T[] | Use Array.newInstance() with unchecked cast |
| Bridge methods | Compiler adds to preserve overrides | Be aware when debugging bytecode |
| Binary compatibility | Pre-generics code works with generics code | No breaking changes to existing JARs |
Observability Checklist
-
Use
javap -con compiled classes to see actual erased signatures and bridge methods -
Check for “unchecked” warnings in compilation output — these signal erasure-related unsafe operations
-
Verify framework serialization does not rely on generic type parameters (most handle erasure via TypeToken / custom serializers)
-
Test with pre-generics code paths if maintaining binary compatibility with older JARs
-
Use
javap -vfor full constant pool and generic attribute debugging
Security Notes
-
No runtime type enforcement:
List<String>andList<Integer>are both justListat runtime. Malicious code that bypasses generics can inject the wrong type. Validate at boundaries. -
Unsafe casts: Erasure means the compiler inserts casts. If you use raw types or
@SuppressWarnings("unchecked"), you bypass these safety nets. Audit for raw type usage in security-critical paths. -
Reflection exposure: Reflection APIs like
Field.getGenericType()returnTypeobjects that include generic info from source, but the actual field in the class file is erased. Do not use generic type info for security decisions.
Pitfalls
-
Class<T>does not retain T at runtime:List<String>.classis illegal. You must pass aClass<String>token explicitly if you need reifiable type information at runtime. -
Varargs and heap pollution:
List<String>[]varargs can cause heap pollution because the array type is erased.@SafeVarargssuppresses the warning but does not fix the issue. -
Confusing errors when bounds are missing: If you call a method on
Tand it is not in the bound, the compile error references erasure — not the original type parameter. -
Generic enums with inheritance: Enum classes cannot extend other types, and generic enum declarations are not allowed because enums inherit
java.lang.Enumwhich is already parameterized.
Quick Recap
-
Type erasure removes all generic type information at compile time
-
Unbounded
<T>becomesObject; bounded<T extends X>becomesX -
The compiler inserts casts at retrieval points and generates bridge methods where needed
-
Runtime sees raw types only —
List<String>andList<Integer>are identical at runtime -
Erasure is the reason you cannot
new T(), useT.class, orinstanceof List<String> -
Erasure was chosen to maintain backward binary compatibility with Java 1.4 and earlier
Interview Questions
::: info
These questions use the .qa-card CSS class structure. Each card has a .qa-question and .qa-answer div.
:::
Model Answer: "Type erasure is the process by which the Java compiler removes all generic type information (`
Model Answer: "Java 1.5 introduced generics without breaking binary compatibility with pre-generics code. With reifiable generics (like C#'s), the generic type is retained at runtime. With erasure, existing compiled `.class` files that used raw types or `Object` casts continue to work with new generic code. The JVM did not need to change; the language did. This was a pragmatic trade-off for adoption."
Model Answer: "Because after erasure, `T` becomes `Object`, and `new Object()` is not the right type. At runtime, there is no way to know what `T` was originally — no token, no class reference. The standard workaround is to pass a `Class
Model Answer: "The bound is used as the erasure replacement for the type parameter: `
Model Answer: "No meaningful effect. After erasure, bytecode is nearly identical to hand-written Object-and-cast code, which the JIT has been optimizing for decades. The generic type checks happen at compile time; the runtime penalty is zero. The JIT sees the erased types and can inline and optimize normally."
Model Answer: "The generic signature attribute is metadata stored in the class file (not in the bytecode itself) that records the generic type information from the source code. It is used only by the compiler — the JVM ignores it at runtime. For example, `Pair
Model Answer: "Yes. For example, `class Container
Model Answer: "Heap pollution occurs when a variable of a parameterized type refers to an object that is not of that type. With generics, it happens when varargs on a generic type creates an array that the compiler cannot verify at runtime: `List
Model Answer: "Reflection sees raw types — `Field.getType()` returns `Object` for a field of type `T` after erasure. `Field.getGenericType()` returns the generic type from the source signature attribute, but this is only metadata the compiler uses, not runtime type information. `Class
Model Answer: "Yes, but only if the erased signatures differ. `
Model Answer: "You cannot use `instanceof` with a parameterized type: `obj instanceof List
Model Answer: "Java chose erasure to maintain binary compatibility with pre-generics code (Java 1.4 and earlier). Adding reified generics to the JVM would have required changing the class file format and breaking existing compiled code. With erasure, existing `.class` files using raw types continue to work with new generic code. The trade-off is losing runtime type information, but the language avoids a breaking change to the platform."
Model Answer: "`super` is only valid in wildcard usage (`? super T`), not in a type parameter declaration. `
Model Answer: "Each level of nesting is erased independently. `Map
Model Answer: "Lambdas can be generic: `Function
Model Answer: "Not natively with standard Java serialization. `ObjectOutputStream` writes raw types — `List
Model Answer: "After erasure, a generic method `T get()` in a superclass becomes `Object get()`. If a subclass overrides it with `String get()`, the signatures do not match. Bridge methods solve this by generating `Object get()` in the subclass that delegates to `String get()`. Without bridges, the override would not be valid after erasure, and polymorphic calls would call the wrong method. Bridge methods restore the override relationship at the cost of synthetic methods in bytecode."
Model Answer: "Because after erasure, method return types are the erased types (e.g., `Object` instead of `T`). At a call site like `String s = box.get()`, the compiler must insert a cast to verify that the retrieved object is actually a `String`. Without the cast, assigning `box.get()` to `String` would be unsafe. Erasure plus cast insertion equals what makes generic code type-safe at compile time while producing pre-generics-compatible bytecode."
Model Answer: "`List>(){}` (anonymous subclass of TypeToken) or passing `Class
>` directly. Some frameworks (like Spring) use `Class.forName()` with parameterized types to resolve generic type information at runtime via the generic signature attribute."
Model Answer: "Type inference is the compiler's ability to deduce type arguments at the call site. Type erasure is what happens to those types after inference — they are stripped. The two are complementary: inference determines what `T` is at compile time; erasure removes `T` at compile time for the bytecode. After erasure, the inferred types no longer exist — they are replaced with `Object` or a bound. The result is that inference works hard at compile time only for the types to be immediately erased when the bytecode is generated."
Further Reading
- Generic Classes — defining classes with type parameters
- Generic Methods — writing flexible methods with type parameters
- Wildcards — unbounded and bounded wildcard type arguments
- Type Bounds — upper and lower bounds on type parameters
- Bridge Methods — compiler-generated methods from type erasure
Summary
Type erasure is the mechanism that makes Java generics work without changing the JVM. At compile time, all generic type information is removed — unbounded T becomes Object, bounded T extends Number becomes Number. The compiler also inserts the casts that would otherwise be written by hand. At runtime, there is no List<String> — only a raw List.
The consequences of erasure shape everything you cannot do with Java generics. No new T(), no T.class, no instanceof List<String>. These are not arbitrary restrictions — they are the natural result of erasing type information before the bytecode is loaded. The JVM simply does not know what T was.
What many miss is that erasure also affects bounds. <T extends Number & Comparable<T>> erases to Number — not Comparable. The secondary bound (Comparable<T>) is recorded in the class file’s generic signature attribute for the compiler’s use, but the field and method signatures use only the first bound’s type. This has real implications when crossing into reflection or dealing with complex generic inheritance chains.
Bridge methods are the direct consequence of erasure. When a subtype overrides a generic method with a more specific return type, the signatures no longer match after erasure. The bridge method restores the override relationship: Object get() delegates to String get(). See Erasure and Bridge Methods in Java for the full mechanics.
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.