Type Erasure in Java

Naveen AnamaneniUpdated · 4 min read

Here's a fact that explains almost every strange rule about generics: the JVM has no idea generics exist. The compiler uses your type arguments to check the code, then erases them, replacing each type parameter with Object (or its bound) and inserting casts where needed. At runtime, a List<String> and a List<Integer> are both just ArrayList. Java did this so that generic code could run on existing JVMs and work with old, non-generic libraries.

Proof That the Types Are Gone

Erased.java▶ Try it
import java.util.ArrayList;
import java.util.List;
 
public class Erased {
    static class Box<T> {
        T value;
        T get() { return value; }
    }
 
    static class NumberBox<T extends Number> {
        T value;
    }
 
    public static void main(String[] args) throws Exception {
        List<String> names = new ArrayList<>();
        List<Integer> marks = new ArrayList<>();
        System.out.println(names.getClass() == marks.getClass());
        System.out.println(names.getClass().getName());
 
        System.out.println(Box.class.getDeclaredField("value").getType());
        System.out.println(Box.class.getDeclaredMethod("get").getReturnType());
        System.out.println(NumberBox.class.getDeclaredField("value").getType());
    }
}
Output
true
java.util.ArrayList
class java.lang.Object
class java.lang.Object
class java.lang.Number

How it works

  1. A List<String> and a List<Integer> have the same runtime class, java.util.ArrayList. The type argument isn't stored in the object.
  2. In the compiled Box<T>, the field T value has become Object value, and get() returns Object.
  3. In NumberBox<T extends Number>, T is erased to its bound, Number.

What the Compiler Writes Instead

Type arguments disappear; the compiler inserts safe casts

javap -c shows the erased calls and the inserted cast for names.add("Navika"); String first = names.get(0);:

javap -c Casts (trimmed)
        11: invokeinterface #12,  2   // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        19: invokeinterface #18,  2   // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        24: checkcast     #22         // class java/lang/String

add takes an Object and get returns an Object, exactly like the old raw List. The checkcast to String is the cast you no longer write yourself. Because the compiler already refused every non-String add, that cast can never fail.

Consequences You'll Meet

Because of erasure...Example
You can't create new T() or new T[10]The runtime doesn't know what T is
You can't use instanceof List<String>The <String> part doesn't exist at runtime
Two overloads can't differ only by type argumentprint(List<String>) and print(List<Integer>) both erase to print(List)
Static fields can't use TThere's one class, shared by every Box<...>
Bridge methods are sometimes generatedSee "Bridge Methods in Java"

Each of these is covered with its compile error in "Restrictions of Generics in Java".

Common Mistakes

Expecting list.getClass() to say ArrayList<String>. It's just ArrayList.

Trying to check obj instanceof List<String>. Check obj instanceof List<?>, then check elements.

Overloading methods on different type arguments. Give them different names.

Interview Corner

What is type erasure?

Type erasure is how Java implements generics: the compiler uses type parameters for compile-time checking, then removes them, replacing each type parameter with its bound or Object, inserting casts where values are read, and generating bridge methods where needed. The resulting bytecode is like pre-generics code, so List<String> and List<Integer> share one runtime class. This kept generics compatible with older JVMs and libraries.

What are the consequences of type erasure?

You can't create instances or arrays of a type parameter, use instanceof with a parameterized type, overload methods that differ only in type arguments, use a type parameter in a static context, or create generic exception classes. Runtime reflection on an object can't recover its type arguments.

Practice

  1. Easy: Print getClass() for a HashMap<String, Integer> and a HashMap<Long, String>, and compare them.
  2. Harder: Write class Holder<T extends CharSequence> with a field T text, and use reflection to print the field's erased type and its generic type.

Quick check: Type erasure

4 questions
1At runtime, what is the class of a List<String> created with new ArrayList<>()?
2What does T become in class Box<T> after erasure?
3What does T become in class NumberBox<T extends Number>?
4Why does the inserted checkcast never fail?

Key Takeaways

Was this tutorial helpful?

Lesson 248 of 482