When the Java code is compiled, prior to producing the bytecode, the Java compiler executes type erasure on the generics. This indicates that the type parameters of the generics are eliminated, ensuring type safety at compile time. Consequently, only the raw types are left in the bytecode, and generic type details are not accessible during runtime.
This article will explore Java generics and type erasure comprehensively.
Contents:
- What are Java Generics?
- What is Type Erasure?
- Types of Type Erasure in Java
- Bridge Methods in Java
- How Type Erasure Impacts Generics Code
- Limitations of Type Erasure
- Heap Pollution in Java
- Non-Reifiable Type
- Conclusion
What are Java Generics?
Java generics enable the creation of classes, interfaces, and methods with type parameters, which guarantees type safety and minimizes the necessity for type casting. They also facilitate the crafting of reusable, maintainable, and efficient code by establishing a placeholder for a type rather than relying on raw types like Objects.
Previously, Java employed raw data types such as List and ArrayList, which resulted in runtime errors from type mismatches when storing various objects. Generics address this challenge by implementing type safety at compile time.
With generics, you can specify a type parameter, ensuring that only a singular type of object can inhabit it. This eliminates the need for explicit type casting and mitigates runtime errors.
Java generics are akin to templates in C++, although they differ in their implementation. Java generics can be categorized into two types, namely:
- Generic Classes
- Generic Methods
What is Type Erasure?
Type erasure in Java refers to the verification of type constraints at compile time and the elimination of type information at runtime. This promotes compatibility with legacy code and prevents the formation of new classes with varying generic types.
Generics were integrated into Java to facilitate type checks during compilation and support generic programming. When generics are utilized, the Java compiler executes type erasure to:
- Substitute all type parameters in generic types with their bounds, or Object if the type parameters are unbounded. Thus, the resulting bytecode solely includes standard classes, interfaces, and methods.
- Add type casts if required to sustain type safety.
- Create bridge methods to maintain polymorphism in extended generic types.
Type erasure guarantees that no new classes are generated for parameterized types, ensuring that the bytecode produced by the compiler is compatible with pre-existing code that does not utilize generics.
Types of Type Erasure in Java
Type erasure within Java transpires in three primary forms:
- Type Parameter Erasure (Class Level)
- Method Type Erasure
- Type Erasure in Wildcards (?)

1. Type Parameter Erasure (Class Level)
When a generic class or interface undergoes compilation, type parameters are discarded and exchanged for either their initial bound type (if defined) or with their object type (if no bound is provided).
Example 1: Unbounded Type Parameter (T)
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
} public T getValue() {
return value;
}
}
After Type Erasure:
class Box {
private Object value; // T is replaced by Object
public void setValue(Object value) {
this.value = value;
} public Object getValue() {
return value;
}
}
In Java, when a class utilizes a generic type (T) without bounds, the compiler substitutes T with Object during compilation. This allows the code to be compatible with older Java versions that do not support generics. After type erasure, all methods reference T as Object, preserving type safety solely at compile time. When values are retrieved, explicit casting is necessary during runtime. Due to T being unbounded, it is substituted with Object.
Example 2: Bounded Type Parameter (T extends Number)
When a bound is designated in the class, T is substituted with its first bound rather than Object.
class Box<T extends Number> {
private T value;
}
After Type Erasure:
class Box {
private Number value; // T is replaced by Number
}
If T extends a Number, the compiler replaces T with the Number instead of Object. This ensures that only numerical values can be utilized. Once type erasure occurs, all methods and fields utilizing T can now reference Number, maintaining type safety for numerical values.
2. Method Type Erasure
When a method contains a generic type parameter, it undergoes…
“`html
the kind of erasure occurs in the same manner as the class-level erasure.
Example 1: Unbounded Generic Method
class Utility { public static <T> void display(T data) { System.out.println(data); } }
Post Type Erasure:
class Utility { public static void display(Object data) { // T is replaced by Object System.out.println(data); } }
In an unbounded generic method, the compiler substitutes T with Object during the erasure process. This allows the method to operate with any data type while ensuring compatibility with legacy Java versions. After erasure, the method functions as if it employs an Object rather than a generic type. Since T has no constraints, it is replaced with Object.
Example 2: Bounded Generic Method (T extends Number)
class MathUtils {
public static <T extends Number> double square(T num) {
return num.doubleValue() * num.doubleValue();
}
}
Post Type Erasure:
class MathUtils { public static double square(Number num) { // T is replaced by Number return num.doubleValue() * num.doubleValue(); } }
In a bounded generic method where T extends Number, this means that T can only be a type of Number. During type erasure, the compiler substitutes T with Number. This enables the method to operate with various number types such as Integer or Double. Following erasure, the method receives a Number as an argument.
3. Type Erasure in Wildcards
In Java generics, wildcards (?) are utilized to signify an unknown type. However, due to type erasure, the information related to the wildcard type is lost at runtime, which influences how Java compiles and enforces type safety.
Example 1: Unbounded Wildcard (<?>)
class Printer {
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
Post Type Erasure:
class Printer {
public static void printList(List list) { // ? is changed to raw type List
for (Object obj : list) {
System.out.println(obj);
}
}
}
In Java, an unbounded wildcard (<?>) is removed during the compilation phase, turning List<?> into a raw List. This enables the method to accept any list but compromises type safety at runtime. All items are treated as Objects, necessitating explicit casting when required.
Example 2: Upper Bounded Wildcard (<? extends Number>)
class Calculator { public static void sum(List<? extends Number> numbers) { double total = 0; for (Number num : numbers) { total += num.doubleValue(); } } }
Post Type Erasure:
class Calculator {
public static void sum(List numbers) { // ? extends Number is transformed to raw List
double total = 0;
for (Object num : numbers) {
total += ((Number) num).doubleValue(); // Explicit casting necessary
}
}
}
In Java, an upper-bounded wildcard (<? extends Number>) is eliminated during compilation, evolving List<?> into a raw List. This enables the passing of any list while eliminating type safety at runtime. All elements are converted to Object, requiring explicit casting to retrieve values.
Bridge Methods in Java
Bridge methods are automatically generated by the Java compiler to sustain polymorphism and type safety while utilizing generics. They also guarantee that code employing generics remains compatible with older Java versions lacking generics support.
When a subclass overrides a method from a generic superclass, type erasure can create an issue in the method signatures. The compiler generates a bridge method to ensure that both the generic and erased versions of the method function correctly.
Example of Bridge Method Creation
1. Prior to Type Erasure (Generic Version)
class Parent<T> {
T data;
void setData(T data) {
this.data = data;
}
}class Child extends Parent<String> {
void setData(String data) { // Overrides Parent<T>'s method
this.data = data;
}
}
2. Following Type Erasure (Generated Bridge Method in Child Class)
class Child extends Parent {
void setData(String data) {
this.data = data;
} // Compiler-generated bridge method
void setData(Object data) {
setData((String) data); // Calls the overridden method
}
}
How Does Type Erasure Impact Generics Code?
Type erasure carries multiple implications for Generics in Java. Below are crucial points to consider:
1. Type Safety
Type erasure upholds type safety at compile time by verifying that only valid types are employed in the generic classes and methods. This aids in averting incorrect type assignments.
Nevertheless, type safety is not guaranteed at runtime since type information is stripped from the bytecode. After type erasure, generic types like T are replaced with Object. Consequently, Java cannot enforce type constraints at runtime, and incorrect casting might trigger ClassCastException errors.
Example:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add(123); // Compile-time error (type safety maintained)
If generics were nonexistent, the code above would not produce an error at compile time, potentially leading to runtime failures.
2. Performance
Type erasure does not enhance the program’s execution speed but maintains a smaller bytecode size by avoiding the creation of multiple versions of generic classes.
Example:
class Box<T> {
private T value;
}
After type erasure, it appears as:
class Box {
private Object value;
}
where T is substituted with Object, preventing separate class versions for different types like Box<String>, Box<Integer>, etc.
3. Compatibility with Legacy Code
Type erasure ensures compatibility with older Java versions that do not support generics by utilizing the existing Object. Since Java converts generic types to Object, generic code compiles into identical bytecode as non-generic code.
Example:
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
Both lists are treated as List at runtime, ensuring compatibility with legacy systems.
“““html
Code that does not incorporate generics. This also implies that the information regarding the generic type is omitted, hence Java cannot differentiate between List<String> and List<Integer> during runtime.
Drawbacks of Type Erasure
While type erasure ensures compatibility, it brings about several notable drawbacks:
1. Instances of Generic Types Cannot Be Created
As T is erased, Java lacks knowledge of its specific type at runtime, making the following disallowed:
class Box<T> {
T value = new T(); // Compilation error
}
However, you can pass the class type as an argument:
class Box<T> {private Class<T> clazz; Box(Class<T> clazz) { this.clazz = clazz; } T createInstance() throws InstantiationException, IllegalAccessException { return clazz.newInstance(); // Works } }
2. Usage of instanceof with Generics is Not Allowed
Java discards generic types, so this verification is not feasible:
A class reference can be utilized instead:
if (obj instanceof T) {
// Compilation error
}
3. Runtime Type Information is Not Retained
Due to type erasure, reflection cannot access generic type parameters.
List<String> list = new ArrayList<>();
System.out.println(list.getClass().getTypeName());
Utilizing TypeToken from Google’s Guava library or explicit class references can help.

Heap Pollution in Java
Heap Pollution transpires in Java when a variable of a parameterized type (like List<String>) points to an object of a different type, resulting in a ClassCastException at runtime.
This situation arises because Java discards generic type data at runtime, treating distinct parameterized types such as List<String> and List<Integer> as identical. It occurs when developers utilize raw types, generic varargs, or unchecked casts. For instance, assigning a raw List to List<String> may permit the addition of non-string values, which could lead to errors subsequently.
Example:
var isMobile = window.innerWidth “); editor4591.setValue(decodedContent); // Set the default text editor4591.clearSelection(); editor4591.setOptions({ maxLines: Infinity });
function decodeHTML4591(input) { var doc = new DOMParser().parseFromString(input, “text/html”); return doc.documentElement.textContent; }
// Function to copy code to clipboard function copyCodeToClipboard4591() { const code = editor4591.getValue(); // Get code from the editor navigator.clipboard.writeText(code).then(() => { jQuery(“.maineditor4591 .copymessage”).show(); setTimeout(function() { jQuery(“.maineditor4591 .copymessage”).hide(); }, 2000); }).catch(err => { console.error(“Error copying code: “, err); }); }
function runCode4591() { var code = editor4591.getSession().getValue(); jQuery(“#runBtn4591 i.run-code”).show(); jQuery(“.output-tab”).click();
jQuery.ajax({ url: “https://intellipaat.com/blog/wp-admin/admin-ajax.php”, type: “post”, data: { language: “java”, code: code, cmd_line_args: “”, variablenames: “”, action: “compilerajax” }, success: function(response) { var myArray = response.split(“~”); var data = myArray[1]; jQuery(“.output4591”).html(“
"+data+"
“); jQuery(“.maineditor4591 .code-editor-output”).show(); jQuery(“#runBtn4591 i.run-code”).hide(); } }) }
function closeoutput4591() { var code = editor4591.getSession().getValue(); jQuery(“.maineditor4591 .code-editor-output”).hide(); }
// Attach event listeners to the buttons document.getElementById(“copyBtn4591”).addEventListener(“click”, copyCodeToClipboard4591); document.getElementById(“runBtn4591”).addEventListener(“click”, runCode4591); document.getElementById(“closeoutputBtn4591”).addEventListener(“click”, closeoutput4591);
Output:

Clarification: In the preceding code, when a list designated for Strings (List<String>) is utilized with a raw type, new ArrayList(). As Java doesn’t maintain type specifics at runtime, it fails to restrict the addition of various types to the list. Subsequently, when Java anticipates a String but encounters a number, it triggers a ClassCastException error.
Non-Reifiable Type
A Reifiable type refers to a type whose type information is completely accessible at runtime. This encompasses primitives, non-generic types, raw types, and invocations of unbound wildcards.
A Non-Reifiable type is a generic type whose type information is discarded at runtime owing to Java’s type erasure. Consequently, the Java Virtual Machine (JVM) does not preserve comprehensive type specifics, regarding List<String> and List<Integer> merely as List.
Given that type specifics are unavailable at runtime, certain operations are disallowed, such as utilizing instanceof with generics. This can result in runtime errors if unchecked casts are employed.
Illustration:
var isMobile = window.innerWidth “);
editor73548.setValue(decodedContent); // Set the default text editor73548.clearSelection();
editor73548.setOptions({ maxLines: Infinity });
function decodeHTML73548(input) { var doc = new DOMParser().parseFromString(input, “text/html”); return doc.documentElement.textContent; }
// Function to copy code to clipboard function copyCodeToClipboard73548() { const code = editor73548.getValue(); // Get code from the editor navigator.clipboard.writeText(code).then(() => { jQuery(“.maineditor73548 .copymessage”).show(); setTimeout(function() { jQuery(“.maineditor73548 .copymessage”).hide(); }, 2000); }).catch(err => { console.error(“Error copying code: “, err); }); }
function runCode73548() { var code = editor73548.getSession().getValue(); jQuery(“#runBtn73548 i.run-code”).show(); jQuery(“.output-tab”).click();
jQuery.ajax({ url: “https://intellipaat.com/blog/wp-admin/admin-ajax.php”, type: “post”, data: { language: “java”, code: code, cmd_line_args: “”, variablenames: “”, action: “compilerajax” }, success: function(response) { var myArray = response.split(“~”); var data = myArray[1]; jQuery(“.output73548”).html(“
" + data + "
“); jQuery(“.maineditor73548 .code-editor-output”).show(); jQuery(“#runBtn73548 i.run-code”).hide(); } }); }
function closeOutput73548() { jQuery(“.maineditor73548 .code-editor-output”).hide(); }
// Attach event listeners to the buttons document.getElementById(“copyBtn73548”).addEventListener(“click”, copyCodeToClipboard73548); document.getElementById(“runBtn73548”).addEventListener(“click”, runCode73548); document.getElementById(“closeoutputBtn73548”).addEventListener(“click”, closeOutput73548);
Result:

Clarification: The code demonstrates that List<String> and List<Integer> are regarded the same at runtime. This occurs because Java eliminates the generic type specifics. Hence, both lists are viewed as List, and stringList.getClass() == integerList.getClass() yields true.
Final Thoughts
From this discussion, we infer that Java discards the generic types at runtime, leaving only the raw types. This assists with compatibility but can engender issues like ClassCastException, heap pollution, and loss of type specifics. Since instanceof does not apply to generics, additional vigilance is warranted. To mitigate these issues, developers should employ bounded types, steer clear of raw types, and limit unchecked casts for safer coding practices.
To delve deeper into this subject, you can consult our Java Course.
Type Erasure in Java – FAQs
The Java compiler removes all type parameters, substituting each with its first bound if the type parameter is bound, or Object if it is unbound.
To offer stricter type checks at compile time and to facilitate generic programming.
Reified generics are supported by the compiler for maintaining the type information, while type erased generics lack this feature.
Besides performance concerns, there is no method to ascertain the type of data in the list during compilation, leading to fragile code. Generics address this concern.
Their ability to identify type errors during compile time improves type security.
The article Type Erasure in Java appeared first on Intellipaat Blog.
“`