Fundamentals 17 min read

How to Retrieve Generic Types at Runtime in Java: Tricks Beyond Type Erasure

This article explains how Java generics and reflection work together, why type erasure hides generic information at runtime, and demonstrates a practical technique using anonymous subclasses and the JVM's Signature attribute to recover the actual generic type.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
How to Retrieve Generic Types at Runtime in Java: Tricks Beyond Type Erasure

Introduction

If you frequently write utility classes, you may need to obtain the runtime type of a generic parameter T. This article starts from Java generics, examines the reflection extensions for generics, and explores the impact of type erasure.

Generics

Generics were introduced in Java 1.5 to provide strong type checking and reusable code without the heap‑pollution problems of pre‑1.5 casts. The formal definition is in the Java Language Specification (JLS), and the informal definition can be illustrated with simple code examples.

/**
 * Example generic class
 * Type Parameter: T extends Number
 * Type Variable: T
 * Type Argument: Foo<Integer> contains Integer
 */
class Foo<T extends Number> {}

Reflection

Since Java 1.5, reflection was extended with the Type interface and related classes to support generic types. The most relevant type for our purpose is ParameterizedType, which represents a concrete generic instance such as Foo<String> or Foo<Integer>.

ParameterizedType UML
ParameterizedType UML

Type Erasure

Java uses type erasure to maintain binary compatibility and avoid generating new bytecode for each generic instantiation. Consequently, generic information is removed at runtime, making it impossible to retrieve the actual type via ordinary reflection.

Generics are compile‑time only; the JVM cannot see the type parameters.

Therefore Class.getGenericSuperclass() returns null for generic type variables.

Technique: Using an Anonymous Subclass

By creating an anonymous subclass of a generic wrapper, the compiler records the concrete type in the class’s Signature attribute. This signature can later be read via reflection to obtain the runtime generic type.

class Wrapper<T> { }

public static Type getGenericRuntimeType(Wrapper<?> wrapper) {
    Type type = wrapper.getClass().getGenericSuperclass();
    if (type instanceof ParameterizedType) {
        return ((ParameterizedType) type).getActualTypeArguments()[0];
    }
    return null;
}

// Usage
Type t1 = getGenericRuntimeType(new Wrapper<List<String>>());          // null
Type t2 = getGenericRuntimeType(new Wrapper<List<String>>() {});    // java.util.List<java.lang.String>

Compiler and JVM Details

The Java compiler writes generic signatures into the Signature attribute of the generated .class file. The JVM’s native method Class.getGenericSignature0() retrieves this attribute, and the reflection API parses it via ClassRepository.

Key steps:

During compilation, javac creates an abstract syntax tree (AST) and resolves generic types.

The ClassWriter writes the resolved generic signature into the class file.

At runtime, Class.getGenericSuperclass() calls getGenericInfo(), which invokes the native getGenericSignature0() to obtain the signature string.

The signature is parsed to produce a ParameterizedType instance.

Conclusion

By understanding Java’s generic type erasure, the compiler’s signature attribute, and the reflection API, we can recover generic type information at runtime using a simple anonymous‑class trick. This approach works without altering method signatures and is compatible with common serialization frameworks such as Jackson and Fastjson.

References

https://docs.oracle.com/javase/tutorial/java/generics/why.html

https://homepages.inf.ed.ac.uk/wadler/gj/Documents/gj-oopsla.pdf

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.4

https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.1.2

https://docs.oracle.com/javase/1.5.0/docs/guide/reflection/enhancements.html

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/class-use/Type.html

https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6

https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1

https://github.com/openjdk/jdk8u

https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#heap_pollution

https://jcp.org/aboutJava/communityprocess/review/jsr014/index.html

https://openjdk.org/groups/compiler/doc/hhgtjavac/index.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JVMcompilerReflectionGenericsType Erasure
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.