A general-purpose utility to retrieve Java generic type values

In a recent post, Arjan Blokzijl discussed how Class.getGenericSuperclass can be used to access generic type information.

This can be very useful, but implementing a general-purpose method to do this isn't as straightforward as it might seem. In this post, we'll outline some of the issues and hopefully resolve them, creating a utility class in the process. After all, with luck this is the kind of code that only needs to be written once! For the curious or impatient, here it is.

Before diving into the details, it should be noted that this utility can only access generic type information available in the class hierarchy at compile time; for example, the Agent in

class SecretAgentVehicle extends Vehicle<Agent>

Java's implementation of generics via type erasure means that it is not possible to access runtime type information - there is no way to get the Agent from

List<Agent> list = new ArrayList<Agent>();

as it is specified neither in the interface List nor ArrayList's superclass AbstractList. I'd love to be wrong about this, by the way - if you have some clever way of accessing this information, please let me know!

Now, as Arjan demonstrated, generic type information is available from ParameterizedType.getActualTypeArguments, where the ParameterizedType is returned by Class.getGenericSuperclass or Class.getGenericInterfaces (you'll need a cast from Type).

However, this only works if the parent is a generic class (respectively, if the class implements a generic interface). If the parent is a "normal" class, the Type returned will simply be the result of Class.getSuperclass (respectively, Class.getInterfaces) and you'll get a ClassCastException trying to cast to ParameterizedType. Ouch!

So what to do in a situation such as

class DoubleOhVehicle extends SecretAgentVehicle

in which we would presumably still like to know that DoubleOhVehicle has type parameter Agent? Well, you simply have to climb up the class hierarchy until you reach the desired class, and a loop would be one way to do this (a recursive implementation is also a good fit). Note that we assume that the input class is, indeed, a SecretAgentVehicle subclass, something that can be enforced using bounded type parameters, for instance.

assert SecretAgentVehicle.class.isAssignableFrom(clazz);

Type supertype = clazz;

do {
  supertype = supertype.getGenericSuperclass();
} while (!supertype.equals(Vehicle.class);

Type[] actualTypeArguments = ((ParameterizedType) supertype).getActualTypeArguments();

Or rather, it would be nice if you could write something like that...but it won't work. Unfortunately, Type doesn't have a getGenericSuperclass method - for the hiearchy traversal Class instances must be explicitly used.

Class<? extends SecretAgentVehicle> clazz;

while (!clazz.equals(SecretAgentVehicle.class)) {
  clazz = clazz.getSuperclass();

Type[] actualTypeArguments = 
  ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments();

Voila! We can now cope with subclasses. What else could there be to do?

Well, consider the following class hierarchy:

class Activity<U, V>

class SecretAgentActivity<S> extends Activity<Agent, S>

class SecretAgentMission extends SecretAgentActivity<Mission>

class BondMission extends SecretAgentMission

Clearly, the (ordered!) type parameters of BondMission are Agent and Mission, but

  ((ParameterizedType) SecretAgentActivity.class.getGenericSuperclass()).getActualTypeArguments();

only returns [Agent, S]. Annoying as this may be, it is, indeed, all that can be expected: SecretAgentActivity.class has no way of knowing that the second parameter is Mission, since that is only defined in a subclass. Note also that the "uninstatiated" variable is returned as S rather than V, i.e. using the type parameter from the subclass.

In order to deal with this situation, therefore, we need not only to traverse up the hierarchy (bearing in mind that not all classes in the hierarchy are necessarily generic), but also keep track of type parameter/instance mappings as we go.
How does this work? Well, in the example, we can compare the type parameters for SecretAgentActivity.class with the actual type arguments to discover that parameter S is mapped to type Mission. Then, when examining the actual parameters [Agent, S] of Activity.class, we can use this mapping to "resolve" S to Mission, obtaining the desired result.

Since our aim is to write a general-purpose method, there is a further question we haven't addressed. Traversing the class hierarchy up from a given class is fine, but...when to stop? In the above example, the question "What is the type parameter for BondMission as a SecretAgentActivity (answer: [Mission])?" may be just as valid as "What are the parameters for BondMission as an Activity ([Agent, Mission])?"
For this reason, the method accepts two arguments: the class whose type arguments are required and the "context" class it is to be regarded a subclass of.

What if the target class and the "context" superclass are the same, or what if someone asks for the types of SecretAgentActivity.class as a subclass of Activity? Well, we know that not all the type variables will be available, but how should the code respond?

In this case, null will be returned for "unresolvable" type parameters (so SecretAgentActivity would have types [Agent, null] as an Activity subclass). This just seems like a reasonable response (besides being easy to implement), but could easily be changed, if desired.

Our discussion up until this point has focused only on class hierarchies. But with so many generic interfaces, our utility would be much more useful if the subclass or the context superclass, or both, could be interfaces. For instance, for a set of classes

interface AgentAttributes<V> extends Map<Agent, V>

interface AgentCodenames extends AgentAttributes<String>

class DigitCodenames implements AgentCodenames

we would also like to be able to answer

  • getActualTypeArguments(DigitCodenames.class, AgentAttributes.class) with [String] and
  • getActualTypeArguments(AgentCodenames.class, Map.class) with [Agent, String].

In principle, this should only require minor changes. One such is that interface type information is available via Class.getGenericInterfaces, not Class.getGenericSuperclass, and we'll need to extract the interface under consideration from the array of interfaces (not all of which are necessarily generic!) returned.

There is a more fundamental difference, though: the inheritance path from a class or interface to a superinterface is not necessarily unique! This is perfectly acceptable

interface Person

interface Assassin extends Person
interface Ladykiller extends Person

interface Agent extends Assassin, Ladykiller

where both [Agent, Assassin, Person] and [Agent, Ladykiller, Person] are paths from Agent to Person.

Which to choose? Well, thankfully, it doesn't matter in this case, because the compiler forbids conflicting (typed) inheritance paths, such as

interface Registry<T> extends Set<T>

interface CodenameRegistry extends Registry<String>
interface DoubleOhRegistry extends Registry<Byte>

interface Mi6Registry extends CodenameRegistry, DoubleOhRegistry

As a result, we can simply choose one of the paths, using ClassUtils.getSuperclassChain.

That link to the code once again.

Comments (11)

  1. florin - Reply

    March 13, 2009 at 6:30 pm

    Looks great.

    Where is the SetUtils.java that is imported?

  2. Andrew Phillips - Reply

    March 13, 2009 at 7:41 pm

    @florin: Eek, yes, thanks for that. I've added a link to SetUtils.java on the source page- it's nothing magical, simply a convenience method like Arrays.asList for Sets...

  3. Dan Howard - Reply

    March 13, 2009 at 10:04 pm

    Tres cool.

  4. erikjan - Reply

    March 22, 2009 at 5:09 pm

    I think there is a way to get the type of List list;

    Execute this code and see that it will print Intger in the end.

    public class FieldSpy {
      public List<Integer> list;
      public static void main(String... args) {
        try {
          Class c = Class.forName("FieldSpy");
          Field f = c.getField("list");
          System.out.format("Type: %s%n", f.getType());
          System.out.format("GenericType: %s%n", f.getGenericType());
          Type type = f.getGenericType();
          if (type instanceof ParameterizedType) {
        } catch (ClassNotFoundException x) {
        } catch (NoSuchFieldException x) {
  5. Andrew Phillips - Reply

    March 23, 2009 at 12:06 pm

    @erikjan: I assume you meant

    List<Integer> list;

    and adjusted your the comment accordingly - I think the angle brackets were stripped out as "invalid HTML" ;-).

    Indeed, running the code prints

    Type: interface java.util.List
    GenericType: java.util.List
    class java.lang.Integer

    as you mention. This also works using Class c = Foo.class.

    Thanks for that, very useful. But it does require you to have access to the field declaration of list, i.e. compile-time information.

    What I meant - and could have expressed more clearly - when I wrote "there is no way to get the Agent from" is that you cannot access the type information if given only the list object.

  6. Erik Jan - Reply

    March 29, 2009 at 2:33 pm

    Ok that's right, I guess that is the only way they could have implemented generics, as Sun always wants to be backwards compatibility. They face the same problems with closures. If only they put in right from the beginning

  7. Neal Gafter - Reply

    April 2, 2009 at 5:04 pm

    Erik: I'm not aware of any compatibility issues that are obstacles to adding closures to Java. Are you?

  8. Andrew Phillips - Reply

    June 21, 2009 at 9:02 pm

    PS: The code for this project is now available at Google Code (in the commons-lang project).

  9. [...] Velmi pěkně popsané pozadí analýzy generik pomocí reflexe [...]

  10. Zing - Reply

    September 25, 2012 at 3:28 am

    I stopped reading at the start of the strikethrough.

  11. Andrew Phillips - Reply

    September 25, 2012 at 9:30 am

    @Zing: Thanks! Seems like the code visualisation plugin in our WordPress installation changed. I hope this version is now more legible.

Add a Comment