Course contents

How type erasure works

Type erasure in javac: bounds replacement, inserted casts, bridge methods, generic array and instanceof limits, heap pollution, reflection signatures and type-token workarounds.

PremiumMust know for interviewsLesson16 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a checked label that gets peeled off
  2. Type erasure under the hood: bounds, casts and bridges
  3. The restrictions erasure creates
  4. Advanced: heap pollution, SafeVarargs and what metadata survives
  5. Determining the Class of a generic type
  6. Why Java chose erasure
  7. How to say it in the interview

🧽 What is type erasure?

Type erasure is the rule that Java generics are mostly checked by javac, then represented in bytecode using ordinary classes, bounds and casts. List<String> and List<Integer> are not two runtime classes; both are List after compilation. It acts like a bouncer checking tickets at the door, then stamping everyone with the same venue wristband once they're inside.

For this lesson, we'll focus on exactly what javac does: replacing type parameters with bounds, inserting casts at use sites, generating bridge methods, and leaving enough signature metadata for reflection. Then we'll connect that to the restrictions you see in code: no new T(), no new T[], no instanceof List<String>, no overloads that differ only by generic argument, shared static fields, no generic exceptions, heap pollution, @SafeVarargs, Class<T> tokens and super type tokens.

Below the line (out of scope)

The 30-second answer

Java chose erasure so generics could be added in Java 5 while old non-generic libraries and bytecode still worked. javac replaces each type parameter with its bound (Object if unbounded, or the first bound), inserts casts where a generic value is read, and emits bridge methods when erasure would otherwise break overriding. The trade-off is that runtime does not know List<String> versus List<Integer>, so you cannot write new T(), new T[], x instanceof List<String>, or overload m(List<String>) and m(List<Integer>). Some generic signatures survive in the class file's Signature attribute, so reflection can read declared field, method and superclass generic types, but a plain new Box<String>() cannot tell you String at runtime. When you need the runtime type, pass it in as Class<T> or capture it with a super type token.

What you’ll learn

  • Trace what javac does during type erasure: replace type parameters, insert casts and generate bridge methods
  • Explain why new T(), new T[], instanceof List<String> and overloads by generic argument do not work
  • Use Class<T>, Array.newInstance and IntFunction<T[]> when erased code needs a runtime type
  • Recognize heap pollution, unchecked warnings and where @SafeVarargs is legitimate
  • Describe what generic metadata survives in the Signature attribute and how reflection reads ParameterizedType
  • Explain the super type token trick used by Gson and Jackson, and why a plain instance cannot reveal T

Interview radar

Asked very often: What is type erasure, and why can't I create new T()? Core follow-up: Why does Comparable<Money> produce a bridge method? Production angle: A generic varargs helper suppresses an unchecked warning it does not understand, then heap pollution turns a clean generic API into a ClassCastException later.

01Picture it first: a checked label that gets peeled off

Picture generics as a label on a shipping box. While the box is in the warehouse, the label says List<String>, and workers are not allowed to put integers in it. When the box reaches the truck, the detailed label is peeled down to List, with a few instructions left on the manifest saying where casts are needed.

That's the mental model: source code is strongly typed, bytecode is compatible. The compiler does the careful checking up front, then emits bytecode that can run beside code compiled before generics existed.

Fig. 1

A generic source class Box flows through javac into erased bytecode where T becomes Object, reads get checkcast instructions, and Comparable gets a bridge compareTo(Object) method

A generic source class Box flows through javac into erased bytecode where T becomes Object, reads get checkcast instructions, and Comparable gets a bridge compareTo(Object) method

Notice the three compiler moves in the picture. Type parameters become bounds. Reads get casts. Bridge methods preserve polymorphism after method signatures have been erased.

02Type erasure under the hood: bounds, casts and bridges

The simplest generic class erases T to Object, because T has no explicit bound.

Try it yourself first

Before reading the erased version, predict where the cast must appear. Is it inside Box, or at the call site?

final class Box<T> {
    private T value;

    Box(T value) {
        this.value = value;
    }

    T get() {
        return value;
    }
}

class BoxUse {
    public static void main(String[] args) {
        Box<String> box = new Box<>("paid");
        String status = box.get();
        System.out.println(status.toUpperCase());
    }
}

Conceptually, javac emits something like this:

final class ErasedBox {
    private Object value;

    ErasedBox(Object value) {
        this.value = value;
    }

    Object get() {
        return value;
    }
}

class ErasedBoxUse {
    public static void main(String[] args) {
        ErasedBox box = new ErasedBox("paid");
        String status = (String) box.get();
        System.out.println(status.toUpperCase());
    }
}

The cast is at the use site, where the compiler knows the target type is String. That is why heap pollution often fails far away from the write that caused it: the bad value can sit inside an erased structure until a read performs the inserted cast.

Bounds change the replacement type. T extends Number erases to Number. Multiple bounds erase to the first bound, so T extends Number & Comparable<T> erases to Number while the extra interface bound stays in the generic signature metadata.

final class NumberBox<T extends Number> {
    private final T value;

    NumberBox(T value) {
        this.value = value;
    }

    double asDouble() {
        return value.doubleValue();   // allowed because erased T is Number
    }
}

Bridge methods, verified with javap

Bridge methods are the part candidates often miss because source code hides them. Suppose a class implements Comparable<Money>:

final class Money implements Comparable<Money> {
    private final long cents;

    Money(long cents) {
        this.cents = cents;
    }

    @Override
    public int compareTo(Money other) {
        return Long.compare(this.cents, other.cents);
    }
}

Comparable<T> erases its method to compareTo(Object). But our source method is compareTo(Money). To keep old callers and polymorphism working, javac generates a synthetic bridge method that accepts Object, casts it to Money, and delegates.

This is real javap -c output from compiling the class above with javac --release 21:

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • The restrictions erasure creates
  • Advanced: heap pollution, SafeVarargs and what metadata survives
  • Determining the Class of a generic type
  • Why Java chose erasure
  • How to say it in the interview

From ₹199 · one-time payment · No auto-renewal

Practise this topicGenerics: interview questions & scenarios
Further reading (8)