liveBook · Manning

Type erasure

As explained in 100 Java Mistakes and How to Avoid Them

Type erasure is Java's compilation process of removing generic type information from runtime representations while preserving compile-time generic checks.

Type erasure removes generic type arguments from Java’s runtime view while retaining generic checks in source code. At execution, a value is not a List<String> or List<E> to the virtual machine; it is a List. A type parameter is replaced by its upper bound, or Object when no bound is declared. section 5.3.2

Erasure does not eliminate every cast. When code reads a generic result, the compiler may insert an implicit cast. A method assigning list.get(0) to String therefore behaves like a raw-List method that casts the returned Object to String. The cast can fail even though no cast expression appears at the call site. section 5.3.2

void printFirstItem(List<String> list) {
  String s = list.get(0);
  System.out.println(s);
}

A generic List<String> read looks type-safe, but its String assignment requires an implicit runtime cast after erasure. section 5.3.2

void printFirstItem(List list) {
  String s = (String) list.get(0);
  System.out.println(s);
}

This desugared form exposes the cast the virtual machine must perform when the generic get result is used as a String. section 5.3.2

The delayed failure

An unchecked cast can corrupt a list without failing immediately. Casting a List<?> to List<Object> effectively disappears at runtime, so an Integer can be added to a list originally declared as List<String>. The later implicit cast in printFirstItem then throws ClassCastException, far from the operation that broke the list. section 5.3.2

List<String> list = new ArrayList<>();
List<?> objects = list;
((List<Object>) objects).add(1);     #1
printFirstItem(list);

This sequence creates generic heap pollution: the unchecked conversion permits an Integer insertion, while the later String read triggers the failure. section 5.3.2

Avoid delayed failures by preserving the generic type contract:

  • Avoid unchecked generic casts; they are not validated by the virtual machine and can fail only at a later implicit cast. section 5.5
  • Use wildcard types when a method needs a compatible generic view rather than a forced concrete type argument. section 5.3.2 section 5.5
  • Use read-only or write-only wrappers where they express the required access without an unchecked conversion. section 5.5
List<String> getList() { … }
void processList(List<? extends CharSequence> list) { … }

A wildcard upper bound accepts a List<String> where a method only needs to read CharSequence values, avoiding a forced List<CharSequence> cast. section 5.3.2

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Traces erased type parameters to Object or bounds and compiler-inserted casts, without detailing broader bytecode generation.
Examples
Uses a List<String> read, an unchecked List<?>-to-List<Object> cast, and a wildcard-bounded method parameter.
What you can do after
Diagnose/debug delayed ClassCastException by tracing earlier unchecked generic casts and replacing them with wildcard or wrapper-based designs.
How it connects
Connects type erasure with generics, raw types, unchecked casts, wildcard types, and ClassCastException.

Also explained in

Compare how 2 books explain type erasure →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.