ProgrammingBackend Developer

What is type erasure in Java Generics? How does it work and what consequences can it lead to in practice?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Generics in Java were introduced in Java 5 to ensure safe operation with collection types and methods, but they were implemented with backward compatibility in mind for previously written bytecode. This was achieved using the type erasure mechanism.

Problem:

The Java compiler requires strict typing, but at runtime, the JVM does not know about the type parameters, and many old classes and libraries, including Java's own collection library, work with raw types. Without backward compatibility, it would have been impossible to support existing developments.

Solution:

Type erasure is the process of converting parameterized types (generics) to their raw versions so that the JVM can work with already existing bytecode without changes. All information about type parameters is removed at compile time, and instead, Object (or a bound if specified via extends) is used.

Example code:

List<String> stringList = new ArrayList<>(); stringList.add("hello"); String s = stringList.get(0); // get returns Object, but the compiler inserts a cast

Key features:

  • At runtime, the JVM does not know the parameters of generics: it is impossible to determine whether it was List<String>, List<Integer>, etc.
  • All type checks are performed at compile time — only the raw type remains at runtime.
  • Type erasure provides backward compatibility but creates limitations and complexities.

Trick questions.

Can method overloading be used only based on generic parameters?

No. Due to type erasure, the compiler considers methods with the same name and different generic parameter types as identical, since the parameters will be erased. For example,

// Compilation error! void process(List<String> list) { } void process(List<Integer> list) { }

Can an array of a generic type be created?

No, not directly. Type erasure does not allow the JVM to hold an array of a specific generic type, for example,

List<String>[] array = new List<String>[10]; // Compilation error

It can be circumvented using an array of raw types, but it is unsafe:

List<String>[] array = (List<String>[]) new List[10];

Can the type of a generic be checked at runtime using instanceof?

No, because the information about parameters is erased. The check:

if (obj instanceof List<String>) { ... } // Compilation error

It is more accurate to check only the base type:

if (obj instanceof List) { ... }

Common mistakes and anti-patterns

  • Attempting to overload methods that differ only in generic parameters.
  • Using arrays of parameterized types.
  • Implicit type casts that lead to ClassCastException errors.

Real-life example

Negative case

A programmer creates an array of parameterized types to store lists of different parameters. Eventually, after a lengthy execution of the program, a ClassCastException occurs when retrieving an object from the array — typing at runtime is not enforced.

Pros:

  • Simple handling of collections during coding.

Cons:

  • Risk of runtime errors.
  • Unpredictable behavior due to the lack of precise typing.

Positive case

Instead of arrays, collections (e.g., List<List<String>>) are used, and all type checks are delegated to the compiler.

Pros:

  • Type safety.
  • Clarity of data structure.

Cons:

  • A slight increase in the number of objects (wrapper collections).