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

