Generic Type
Runtime
Programming
Class Identification
Software Development

Get generic type of class at runtime

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Getting a generic type at runtime depends heavily on the language and on how the generic system is implemented. In Java, this question usually runs into type erasure, which means many generic type details do not exist at runtime unless you preserve them explicitly.

Why the Type Is Often Unavailable

Java generics improve compile-time safety, but most parameterized type information is erased from ordinary objects at runtime. For example, List<String> and List<Integer> both become just List at runtime.

java
1import java.util.ArrayList;
2import java.util.List;
3
4public class ErasureExample {
5    public static void main(String[] args) {
6        List<String> strings = new ArrayList<>();
7        List<Integer> integers = new ArrayList<>();
8
9        System.out.println(strings.getClass() == integers.getClass());
10    }
11}

Output:

text
true

That is the core reason this problem exists.

Strategy 1: Pass a Class<T> Token

The most reliable solution is to keep the type yourself by passing a Class<T> value into the object or method.

java
1public class Box<T> {
2    private final Class<T> type;
3
4    public Box(Class<T> type) {
5        this.type = type;
6    }
7
8    public Class<T> getType() {
9        return type;
10    }
11
12    public static void main(String[] args) {
13        Box<String> box = new Box<>(String.class);
14        System.out.println(box.getType().getName());
15    }
16}

This works because you are not asking Java to recover erased information. You are preserving the type token explicitly.

Strategy 2: Inspect a Parameterized Superclass

If a class directly extends a generic base type with concrete arguments, that information can often be read through reflection.

java
1import java.lang.reflect.ParameterizedType;
2import java.lang.reflect.Type;
3
4abstract class Repository<T> {
5    public Type getModelType() {
6        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
7        return type.getActualTypeArguments()[0];
8    }
9}
10
11class UserRepository extends Repository<String> {
12}
13
14public class Main {
15    public static void main(String[] args) {
16        UserRepository repo = new UserRepository();
17        System.out.println(repo.getModelType());
18    }
19}

This works only when the generic information is still present in the class hierarchy. It does not recover erased types from arbitrary object instances.

Strategy 3: Use a Type Reference Pattern

Frameworks such as Jackson and Guava use a “type token” or “type reference” pattern to preserve full parameterized types such as List<String>.

java
1import java.lang.reflect.ParameterizedType;
2import java.lang.reflect.Type;
3
4abstract class TypeReference<T> {
5    private final Type type;
6
7    protected TypeReference() {
8        ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
9        this.type = p.getActualTypeArguments()[0];
10    }
11
12    public Type getType() {
13        return type;
14    }
15
16    public static void main(String[] args) {
17        TypeReference<java.util.List<String>> ref = new TypeReference<>() {};
18        System.out.println(ref.getType());
19    }
20}

This is how libraries preserve nested generic information that a simple Class<T> token cannot represent.

What Will Not Work

Trying to ask a plain instance for its original type argument usually fails.

java
List<String> values = new ArrayList<>();
System.out.println(values.getClass());

This tells you the runtime class, not the generic argument. If you did not preserve the type somewhere else, Java cannot reconstruct it for you.

Choosing the Right Approach

Use Class<T> when a raw class token is enough, such as String.class or User.class. Use a type-reference approach when you need full generic information such as Map<String, Integer>. Use reflective superclass inspection only when your class hierarchy actually captures the generic argument.

Common Pitfalls

The most common mistake is expecting obj.getClass() to reveal generic type arguments. It cannot recover erased type parameters.

Another mistake is assuming a Class<T> token can represent nested parameterized types. It can represent String.class, but not List<String>.class, because that syntax does not exist.

Developers also sometimes use reflection against a superclass without checking whether the superclass is actually parameterized in that context, which can lead to ClassCastException.

Summary

  • In Java, generic type information is often erased at runtime.
  • The safest solution is to preserve type information explicitly with a Class<T> token or type reference.
  • Reflective superclass inspection works only when the class hierarchy retains the generic argument.
  • 'obj.getClass() reveals the runtime class, not erased generic parameters.'
  • Choose the preservation strategy based on whether you need a raw class or a full parameterized type.

Course illustration
Course illustration