What is type erasure
After compilation, the JVM doesn't know what type was specified in the generic.
🟢 Junior Level
Type erasure is a process where the Java compiler removes generic type information during compilation.
After compilation, the JVM doesn’t know what type was specified in the generic.
// Your code:
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// After compilation (type erasure):
List strings = new ArrayList(); // String -> Object
List integers = new ArrayList(); // Integer -> Object
// JVM sees just List, without type information!
Why it’s needed:
- Compatibility with old code (before Java 5)
- No overhead at runtime
- Code works just as fast
🟡 Middle Level
How it works
The compiler replaces type parameters with their bounds (or Object if there is no bound):
// Source code
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
// After type erasure
public class Box {
private Object value; // T -> Object (no bound)
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
Bounded type erasure:
public class NumberBox<T extends Number> {
private T value;
public double doubleValue() { return value.doubleValue(); }
}
// After erasure: T -> Number (first bound)
public class NumberBox {
private Number value;
public double doubleValue() { return value.doubleValue(); }
}
Compiler adds casts:
Box<String> box = new Box<>();
box.set("Hello");
String s = box.get();
// After compilation:
Box box = new Box();
box.set("Hello");
String s = (String) box.get(); // implicit cast added
Restrictions due to type erasure
1. Cannot create instance of T:
public class Box<T> {
// ❌ Cannot do
public Box() {
value = new T(); // compilation error
}
}
2. Cannot instanceof T:
public void check(Object obj) {
// ❌ Cannot do
if (obj instanceof T) { } // compilation error
}
3. Cannot create array of T:
public class Box<T> {
// ❌ Cannot do
private T[] array = new T[10]; // compilation error
// ✅ Solution
private T[] array = (T[]) new Object[10];
}
4. Cannot have static field with T:
public class Box<T> {
// ❌ Cannot do — static context doesn't know about T
private static T value; // compilation error
}
🔴 Senior Level
Internal Implementation
JLS Specification (4.6):
Type erasure mapping:
1. Type parameter -> first bound (or Object)
2. Parameterized type -> erasure of raw type
3. Nested type -> recursively applied
Bridge methods:
public class Node<T extends Comparable<T>> {
private T data;
public void setData(T data) { this.data = data; }
public T getData() { return data; }
}
public class DateNode extends Node<Date> {
@Override
public void setData(Date data) { super.setData(data); }
@Override
public Date getData() { return super.getData(); }
}
// After erasure:
class Node {
private Comparable data;
public void setData(Comparable data) { }
public Comparable getData() { return data; }
}
class DateNode extends Node {
public void setData(Date data) { super.setData(data); }
public Date getData() { return super.getData(); }
// Bridge method to preserve polymorphism:
// Bridge method with erased signature that delegates to the specific one
public void setData(Comparable data) { setData((Date) data); }
public Comparable getData() { return getData(); }
// In DateNode the compiler generates:
// public Object getData() { return this.getData(); } // calls Date getData()
// This is NOT recursion — it calls the overloaded method with a different return signature.
}
Reflection:
// Check via reflection
public void inspect() {
List<String> list = new ArrayList<>();
// Type erasure — runtime doesn't know about String
Type genericType = list.getClass().getGenericSuperclass();
System.out.println(genericType); // java.util.AbstractList<E>
// But you can get information from field/method
Field field = MyClass.class.getDeclaredField("stringList");
ParameterizedType pt = (ParameterizedType) field.getGenericType();
System.out.println(pt.getActualTypeArguments()[0]); // class java.lang.String
}
Architectural Trade-offs
Type erasure vs reified generics:
| Type Erasure (Java) | Reified (C#, Kotlin) |
|---|---|
| Binary compatibility | Full information at runtime |
| Zero overhead | Runtime overhead |
| Restrictions (new T, instanceof) | Full functionality |
| Bridge methods | No bridge methods |
Edge Cases
1. Generic array creation exception:
public class GenericArray<T> {
private T[] elements;
public GenericArray(int size) {
// Cannot new T[], but can via Class
elements = (T[]) Array.newInstance(
Object.class, size
);
// This is still an unchecked cast. Array.newInstance(Object.class) creates Object[],
// not T[]. A type-safe approach requires Class<T>.
}
}
2. Generic exception handling:
public class ExceptionHandler {
// Type erasure — cannot catch by generic type
public void handle(Exception e) {
// ❌ Cannot do
// if (e instanceof RuntimeException<String>) { }
// ✅ Check via class
if (e instanceof RuntimeException) { }
}
}
3. Heap pollution:
// Heap pollution — when generic collection contains wrong type
List rawList = new ArrayList();
rawList.add("Hello");
List<String> stringList = rawList; // unchecked warning
String s = stringList.get(0); // OK
rawList.add(42); // heap pollution — Integer appeared in a list of Strings
Integer i = (Integer) stringList.get(1); // ClassCastException!
// @SafeVarargs helps
@SafeVarargs
public static <T> void safeMethod(List<T>... lists) { }
Performance
Type erasure overhead:
- Compile time: type checking
- Runtime: Zero overhead
- Memory: No extra data
- Cast: Implicit casts on access
Benchmark:
Operation | With generics | Without generics
------------------|---------------|-----------------
List.get() | 1 ns | 1 ns (+ cast)
Map.put() | 5 ns | 5 ns
Type check | compile time | runtime (ClassCastException risk)
Production Experience
Workarounds for type erasure:
// 1. Pass Class<T> for creating instances
public class Factory<T> {
private final Class<T> type;
public Factory(Class<T> type) {
this.type = type;
}
public T create() throws Exception {
return type.getDeclaredConstructor().newInstance();
}
}
// 2. Type token for generics
public class TypeReference<T> {
private final Type type;
protected TypeReference() {
ParameterizedType pt = (ParameterizedType)
getClass().getGenericSuperclass();
this.type = pt.getActualTypeArguments()[0];
}
public Type getType() { return type; }
}
// Usage (Jackson style)
User user = mapper.readValue(json, new TypeReference<User>() {});
Best Practices
// ✅ Use Class<T> for creating instances
public <T> T create(Class<T> type) { return type.newInstance(); }
// ✅ Wildcards for flexibility
public void process(List<? extends Number> numbers) { }
// ✅ @SafeVarargs for generic varargs
@SafeVarargs
public static <T> void safeMethod(T... items) { }
// ❌ new T()
// ❌ instanceof T
// ❌ new T[]
// ❌ Static context with T
🎯 Interview Cheat Sheet
Must know:
- Type erasure — compiler replaces T with Object (or first bound) at compilation
- Why: binary compatibility with pre-Java 5 code, zero runtime overhead
- Restrictions: cannot
new T(),instanceof T,new T[], static field with T - Bridge methods — compiler creates them to preserve polymorphism after erasure
- Bounded erasure:
<T extends Number>— T is replaced with Number, not Object - Heap pollution — when generic collection contains elements of wrong type
Common follow-up questions:
- Why can’t you new T()? — After erasure T -> Object, compiler doesn’t know which constructor to call
- Can you determine type T at runtime? — Not directly, but can via Class
or TypeReference - What is a bridge method? — Synthetic method with erased signature, delegating to the original
- Type erasure vs reified generics (C#)? — Java: compile-time only, C#: full information at runtime
Red flags (DO NOT say):
- ❌ “JVM checks generic types at runtime” — Type erasure, JVM doesn’t see generic types
- ❌ “new T() works via reflection” — Forbidden at compiler level
- ❌ “Type erasure adds overhead” — Zero overhead, no extra data
- ❌ “Can check instanceof T” — Compilation error, type is erased
Related topics:
- [[11. What are Generics in Java]]
- [[12. What are the advantages of using Generics]]
- [[14. Can you create an array of generic type]]
- [[20. What happens when you try to create an instance of generic type via new T()]]
- [[25. What are bridge methods and why are they needed]]