Definition
- Erasure process – the compiler replaces
TwithObject(or the upper bound if bounded:T extends NumberbecomesNumber), inserts casts where needed, and generates bridge methods - Same class at runtime –
ListandListare bothArrayListat runtime;list.getClass()returns the same Class object - Restrictions – no
new T(), noinstanceof T, noT.class, no generic exception types, no generic array creation (new T[]) - Bridge methods – synthetic methods generated to maintain polymorphism when a subclass narrows a type parameter; they delegate from the erased signature to the specific one
- Backward compatibility – erasure ensures that generic code interoperates with pre-Java-5 raw type code without breaking existing bytecode
Generics are like coloured labels on filing drawers at National Bank of India during work hours (compile time). The green label says "Savings accounts only" and the red label says "Loan documents only". The compiler uses these labels to prevent Rajesh from putting a loan document in the savings drawer. But at the end of the day (runtime), the cleaning crew rips off all labels (erasure). Every drawer just says "Files". You cannot ask "Is this the savings drawer?" because the label is gone.
Understanding the Concepts
What is type erasure?
Type erasure is the process by which the Java compiler removes all generic type information after performing compile-time type checking. The resulting bytecode contains no traces of type parameters. This was a deliberate design choice for backward compatibility with pre-Java-5 code.
package com.bank.demo;
import java.util.ArrayList;
import java.util.List;
public class TypeErasureDemo {
public static void main(String[] args) {
List<String> accounts = new ArrayList<>();
List<Integer> amounts = new ArrayList<>();
// At runtime, both are the same class
System.out.println("accounts class: " + accounts.getClass().getName());
System.out.println("amounts class: " + amounts.getClass().getName());
System.out.println("Same class: " + (accounts.getClass() == amounts.getClass()));
}
}
accounts class: java.util.ArrayList
amounts class: java.util.ArrayList
Same class: true
How does the compiler erase type parameters?
The compiler replaces each type parameter with its bound. If the type parameter is unbounded (), it becomes Object. If it has a bound (), it becomes Number. The compiler also inserts casts at call sites.
// SOURCE CODE:
public class AccountBox<T> {
private T account;
public T getAccount() { return account; }
public void setAccount(T account) { this.account = account; }
}
// AFTER ERASURE (bytecode equivalent):
public class AccountBox {
private Object account;
public Object getAccount() { return account; }
public void setAccount(Object account) { this.account = account; }
}
// SOURCE CODE with bound:
public class NumberBox<T extends Number> {
private T value;
public T getValue() { return value; }
}
// AFTER ERASURE:
public class NumberBox {
private Number value; // T replaced with Number (the bound)
public Number getValue() { return value; }
}
At the call site, the compiler inserts a cast:
// Source:
AccountBox<String> box = new AccountBox<>();
String name = box.getAccount();
// Bytecode equivalent:
AccountBox box = new AccountBox();
String name = (String) box.getAccount(); // compiler-inserted cast
What can you NOT do because of type erasure?
Type erasure creates several restrictions because the JVM has no knowledge of generic types at runtime.
package com.bank.demo;
import java.util.List;
public class ErasureRestrictionsDemo {
// RESTRICTION 1: Cannot instantiate T
static <T> T createInstance() {
// return new T(); // COMPILE ERROR: cannot instantiate type parameter
return null;
}
// WORKAROUND: pass Class<T>
static <T> T createInstance(Class<T> clazz) throws Exception {
return clazz.getDeclaredConstructor().newInstance();
}
// RESTRICTION 2: Cannot use instanceof with parameterised type
static void checkType(Object obj) {
// if (obj instanceof List<String>) {} // COMPILE ERROR
if (obj instanceof List<?>) { // OK: unbounded wildcard
System.out.println("It is a List of something");
}
}
// RESTRICTION 3: Cannot create generic array
static <T> void createArray() {
// T[] arr = new T[10]; // COMPILE ERROR
// List<String>[] arr = new List<String>[10]; // COMPILE ERROR
}
// RESTRICTION 4: Cannot create generic exception
// class BankException<T> extends Exception {} // COMPILE ERROR
public static void main(String[] args) throws Exception {
// Workaround for new T()
String instance = createInstance(String.class);
System.out.println("Created: '" + instance + "'");
checkType(List.of("SAV2001"));
}
}
Created: ''
It is a List of something
What are bridge methods?
Bridge methods are synthetic methods the compiler generates to maintain polymorphism after erasure. When a class implements a generic interface and narrows the type parameter, the erased interface method has a different signature than the class method. The compiler generates a bridge that delegates.
package com.bank.demo;
import java.lang.reflect.Method;
import java.util.Arrays;
public class BridgeMethodDemo {
interface Comparable<T> {
int compareTo(T o);
}
// After erasure: interface Comparable { int compareTo(Object o); }
static class Account implements Comparable<Account> {
String id;
Account(String id) { this.id = id; }
@Override
public int compareTo(Account other) {
return this.id.compareTo(other.id);
}
// Compiler generates bridge method:
// public int compareTo(Object o) { return compareTo((Account) o); }
}
public static void main(String[] args) {
// Show that the bridge method exists via reflection
Method[] methods = Account.class.getDeclaredMethods();
for (Method m : methods) {
if (m.getName().equals("compareTo")) {
System.out.println(m + " | bridge: " + m.isBridge());
}
}
Account a = new Account("SAV2001");
Account b = new Account("CUR3001");
System.out.println("compareTo: " + a.compareTo(b));
}
}
public int BridgeMethodDemo$Account.compareTo(BridgeMethodDemo$Account) | bridge: false
public int BridgeMethodDemo$Account.compareTo(java.lang.Object) | bridge: true
compareTo: 1
How is this different from C# reified generics?
| Feature | Java (erased) | C# (reified) |
|---|---|---|
| Type info at runtime | Not available | Available |
| new T() | Not possible | Possible with new() constraint |
| typeof(List
| Runtime type check | instanceof List> only | typeof(List
| Backward compat | Seamless with raw types | Required new runtime |
What are common mistakes?
Mistake: Trying to create instances of T
// WRONG: T is erased at runtime
public class Repository<T> {
public T createEmpty() {
return new T(); // COMPILE ERROR
}
}
// CORRECT: pass Class<T> and use reflection
public class Repository<T> {
private final Class<T> entityClass;
public Repository(Class<T> entityClass) { this.entityClass = entityClass; }
public T createEmpty() throws Exception {
return entityClass.getDeclaredConstructor().newInstance();
}
}
Mistake: Using instanceof with parameterised types
// WRONG
if (obj instanceof List<String>) { } // COMPILE ERROR
// CORRECT: use unbounded wildcard
if (obj instanceof List<?> list) { // pattern matching (Java 16+)
System.out.println("List of size: " + list.size());
}
What should a senior engineer know?
- Reifiable vs non-reifiable types. A reifiable type is one whose type information is fully available at runtime (String, List>, int[]). A non-reifiable type has some information erased (List
, Map ). Arrays require reifiable component types, which is why new Listis illegal.[10] - Super type tokens. Frameworks like Jackson and Gson use
TypeReferenceor- >
TypeTokento capture generic type information via anonymous subclasses, working around erasure. - Valhalla project. Project Valhalla aims to bring specialised generics to Java, where
Listwould store primitives directly without boxing, partially addressing erasure limitations.
What are common interview follow-up questions?
- "Why did Java choose type erasure?" : For backward compatibility with pre-Java-5 code. Existing bytecode and libraries continued to work without changes.
- "Can you determine the generic type at runtime?" : Not directly. You can use super type tokens (anonymous subclass trick) or pass Class
explicitly. - "What is a bridge method?" : A synthetic method generated by the compiler to bridge the erased signature and the specific signature, preserving polymorphism.
- "Why can you not create a generic array?" : Arrays are reified (know their component type at runtime) and throw ArrayStoreException on type violations. Generic arrays would break this guarantee because the type is erased.
Visual Overview
Type Erasure Process
=====================
Source code: Bytecode (after erasure):
┌──────────────────────┐ ┌──────────────────────┐
│ class Box<T> { │ │ class Box { │
│ T item; │ ──────▶ │ Object item; │
│ T get() {} │ │ Object get() {} │
│ void set(T t) {} │ │ void set(Object) {}│
│ } │ │ } │
└──────────────────────┘ └──────────────────────┘
Box<String> b = new Box<>(); Box b = new Box();
String s = b.get(); String s = (String) b.get();
// compiler inserts cast
Bridge Method:
┌──────────────────────────────────────────────────────┐
│ interface Comparable<T> { int compareTo(T o); } │
│ After erasure: int compareTo(Object o) │
│ │
│ class Account implements Comparable<Account> { │
│ int compareTo(Account o) { ... } │
│ int compareTo(Object o) { return compareTo((Account)o); } │
│ ^^^^ bridge method (synthetic) │
└──────────────────────────────────────────────────────┘
Key Takeaway
Type erasure is the mechanism by which Java generics provide compile-time type safety without changing the runtime bytecode format. The compiler checks all type constraints, inserts necessary casts, and then removes all type parameters. At runtime, List
This creates several restrictions: you cannot write new T() (the JVM does not know what T is), you cannot use instanceof with parameterised types, you cannot create generic arrays, and you cannot create generic exception classes. The workaround for new T() is to pass Class
At National Bank of India, Rajesh's generic Repository
Remember one sentence: type erasure removes all generic type information at compile time for backward compatibility, making List
---