Skip to content
Luke Hutchison edited this page Aug 11, 2026 · 16 revisions

See also the ClassGraph API overview.

ClassGraph parses classfile-internal type signature and type descriptor strings (like "Ljava/lang/String;") into type signature objects ClassTypeSignature, MethodTypeSignature, and TypeSignature.

Contents

Type signature class hierarchy

The hierarchy of type signature classes reflects the structure of Java's internal type signature system.

Type signature class hierarchy

Example usage

The following methods can be used to get a TypeSignature (getTypeSignatureOrTypeDescriptor() is preferable to calling either getTypeSignature() or getTypeDescriptor(), since it will use the type signature, including generic information, if available, otherwise it will fall back to using the type descriptor, without generic information):

  • Generic class type signature: ClassInfo#getTypeSignature() -- returns a ClassTypeSignature for the type of the class, if the class is generic, otherwise returns null. A class has no type descriptor in the classfile, so ClassInfo#getTypeDescriptor() synthesizes one from the class name, the superclass name and the implemented interface names, and ClassInfo#getTypeSignatureOrTypeDescriptor() falls back to that for non-generic classes.
  • Field type signature: FieldInfo#getTypeSignatureOrTypeDescriptor() -- returns a TypeSignature for the type of the field.
  • Method type signature:
    • Method result type signature: call MethodInfo#getTypeSignatureOrTypeDescriptor() to get a MethodTypeSignature for the type of the method, then MethodTypeSignature#getResultType() to get the result type of the method as a TypeSignature.
    • Method parameter type signatures: call MethodInfo#getParameterInfo() to get a List of MethodParameterInfo objects for the method parameters, then for each method parameter, call MethodParameterInfo#getTypeSignatureOrTypeDescriptor() to get a TypeSignature for the method parameter.

For example, to check the result type of a method:

try (ScanResult scanResult = new ClassGraph().enableAllInfo()
        .acceptPackages(packageName).scan()) {
    ClassInfo classInfo = scanResult.getClassInfo(className);
    MethodInfoList methodInfoList = classInfo.getMethodInfo(methodName);
    MethodInfo methodInfo = methodInfoList.get(0);  // First method named {methodName}
    MethodTypeSignature methodTypeSignature = methodInfo
            .getTypeSignatureOrTypeDescriptor();
    TypeSignature resultTypeSignature = methodTypeSignature.getResultType();
    if (resultTypeSignature instanceof ArrayTypeSignature arrayTypeSignature) {
        System.out.println("Method " + methodInfo.getName() + " returns a "
                + arrayTypeSignature.getNumDimensions() + "-dimensional array of "
                + arrayTypeSignature.getElementTypeSignature());
    } else if (resultTypeSignature instanceof BaseTypeSignature baseTypeSignature) {
        System.out.println("Method " + methodInfo.getName() + " returns "
                + baseTypeSignature.getTypeName());
    } else if (resultTypeSignature instanceof ClassRefTypeSignature classRefTypeSignature) {
        System.out.println("Method " + methodInfo.getName() + " returns "
                + classRefTypeSignature.getFullyQualifiedClassName());
    } else if (resultTypeSignature instanceof TypeVariableSignature typeVariableSignature) {
        // Attempt to resolve type variable
        TypeParameter typeParameter = typeVariableSignature.resolve();
        System.out.println("Method " + methodInfo.getName()
                + " returns type variable " + typeVariableSignature.getName()
                + ", which resolves to " + typeParameter);
    }
}

You probably have to cast a value of TypeSignature returned by the ClassGraph API into the correct subclass type (e.g. ClassRefTypeSignature) to get any useful information. For example:

public class TestReadingTypeArgs {
    static class A<X> {
    }

    static abstract class B {
        abstract A<Integer> a();
    }

    public static void main(String[] args) {
        try (ScanResult scanResult = new ClassGraph()
                .acceptPackages(TestReadingTypeArgs.class.getPackage().getName())
                .enableAllInfo().scan()) {
            ClassInfo bClass = scanResult.getClassInfo(B.class.getName());
            MethodInfo aMethodInfo = bClass.getMethodInfo("a").get(0);
            MethodTypeSignature aType = aMethodInfo.getTypeSignatureOrTypeDescriptor();
            TypeSignature aResultType = aType.getResultType();
            ClassRefTypeSignature aResultTypeConcrete = (ClassRefTypeSignature) aResultType;
            String aTypeBaseClassName = aResultTypeConcrete.getBaseClassName();
            List<TypeArgument> aTypeArgs = aResultTypeConcrete.getTypeArguments();
            TypeArgument aTypeArg0 = aTypeArgs.get(0);
            String aTypeArg0BaseClassName =
                    ((ClassRefTypeSignature) aTypeArg0.getTypeSignature()).getBaseClassName();
            System.out.println("Method a() returns type " + aTypeBaseClassName
                    + " with argument " + aTypeArg0BaseClassName);
        }
    }
}

This prints:

Method a() returns type TestReadingTypeArgs$A with argument java.lang.Integer

Resolving type variables

ClassGraph does not substitute type arguments into type parameters automatically, so you may get a TypeVariableSignature for the type signature of a field, method, method parameter, etc., where you were expecting a concrete type. This is not an omission: a type variable has no single concrete value. Given

interface Base<T> {
    T getT();
}

class DerivedA implements Base<String>  { }
class DerivedB implements Base<Integer> { }

the result type of Base#getT() is genuinely just T. It is String only when viewed through DerivedA, and Integer only when viewed through DerivedB.

TypeSignature#resolveTypeVariables(ClassInfo contextClass)

Use this method to substitute the type arguments that a particular subtype supplies:

ClassInfo base = scanResult.getClassInfo("Base");
ClassInfo derivedA = scanResult.getClassInfo("DerivedA");

TypeSignature resultType = base.getMethodInfo("getT").get(0)
        .getTypeSignatureOrTypeDescriptor().getResultType();

resultType.toString();                                    // "T"
resultType.resolveTypeVariables(derivedA).toString();      // "java.lang.String"

contextClass is the subtype you are viewing the declaring class through — it is the answer to "what type does this method return when called on a DerivedA?". It is not an enclosing class, and it is required rather than inferred because a MethodInfo or FieldInfo belongs to the class that declares it and is shared by every subclass that inherits it, so it does not itself record which subtype you reached it through.

Because the method is defined on TypeSignature, it applies equally to method result types, method parameter types and field types:

// Method parameter type
methodInfo.getParameterInfo().get(0).getTypeSignatureOrTypeDescriptor()
        .resolveTypeVariables(contextClass);

// Field type
fieldInfo.getTypeSignatureOrTypeDescriptor().resolveTypeVariables(contextClass);

Resolution walks up the superclass and superinterface chain of the context class, composing the type arguments each level supplies for the level above it, so bindings passed through intermediate classes are resolved too:

class Mid<U> implements Base<U> { }
class Derived extends Mid<Integer> { }   // Base's T resolves to Integer

Type variables are substituted inside type arguments at any depth (Map<A, List<B>>) and inside array element types (T[][]). The original type signature is never modified — a new one is returned, or the same object if nothing was substituted.

A type variable is deliberately left unchanged when it cannot be resolved:

  • the context class is not a subtype of the class declaring the type variable;
  • the supertype is used in raw form, so supplies no type argument;
  • the type variable is shadowed by a type parameter of the method that declares the type (<T> T identity(T t) declares its own T);
  • the type argument is a wildcard with no equivalent outside type-argument position — ? or ? super X. An upper-bounded wildcard ? extends X resolves to X. Within type-argument position, wildcards are substituted verbatim, so List<T> with T := ? extends Number resolves to List<? extends Number>;
  • the type variable is one that an inner class inherits from its enclosing class, since the classfile records it as declared by the inner class, which is not on the supertype chain of a subclass of the enclosing class.

Other options

TypeVariableSignature#resolve() resolves a type variable to its declaration (its TypeParameter), by looking first at the containing method and then at the containing class. That gives you the variable's bounds, not a substituted type argument.

If you are willing to load the classes, you can instead use the reflection API together with a library like gentyref / GeAnTyRef, which can find the concrete and generic type signature of a loaded class.

Type signature classes

ClassTypeSignature

A type signature for a generic class, returned by ClassInfo#getTypeSignature().

  • .getTypeParameters() returns the type parameters of the class as a List of TypeParameter objects.
  • .getSuperclassSignature() returns a ClassRefTypeSignature for the superclass of this class.
  • .getSuperinterfaceSignatures() returns a List of ClassRefTypeSignature objects for the interfaces implemented by this class.

MethodTypeSignature

A type signature for a method, returned by MethodInfo#getTypeSignature() or MethodInfo#getTypeSignatureOrTypeDescriptor().

  • .getTypeParameters() returns the type parameters of the method as a List of TypeParameter objects, or the empty list if the method is not generic.
  • .getResultType() returns a TypeSignature for the result type of the method.
  • .getThrowsSignatures() returns a List of ClassRefOrTypeVariableSignature objects for any exceptions or errors that can be thrown by the method.
  • To obtain method parameter type signatures, first call MethodInfo#getParameterInfo() to obtain a List of MethodParameterInfo objects, one per method parameter, and then call MethodParameterInfo#getTypeSignatureOrTypeDescriptor() on each of them.
  • .getReceiverTypeAnnotationInfo() returns an AnnotationInfoList of any type annotations on an explicit receiver parameter of the method, if present, otherwise returns null.

TypeSignature

A type signature or type descriptor, representing the type of a field, the return type of a method, the type of a method parameter, etc. Subclasses are ReferenceTypeSignature and BaseTypeSignature.

All subclasses of TypeSignature include the following methods:

  • .resolveTypeVariables(ClassInfo contextClass) returns this type signature with any type variables substituted by the type arguments that contextClass supplies for them, or this type signature itself if nothing could be substituted. See Resolving type variables.
  • .equalsIgnoringTypeParams(TypeSignature other) returns true if this type signature and the other type signature are equal, ignoring any type parameters. A type variable is considered equal to a class reference if the type variable's bound can be reconciled with that class, so that a type signature parsed from a generic signature can be compared with the corresponding erased type descriptor.

The following two methods are available on every type signature class on this page, including ClassTypeSignature, MethodTypeSignature, TypeArgument and TypeParameter, which are not subclasses of TypeSignature:

  • .getTypeAnnotationInfo() returns an AnnotationInfoList of any type annotations on the type signature, if present, otherwise returns null.
  • .toStringWithSimpleNames() returns a simpler rendering of the type signature than toString(), by using only the simple name of any classes or annotations in the type signature.

BaseTypeSignature

A type signature for a base type (a primitive type or void).

  • .getType() returns int.class, long.class, short.class, double.class, float.class, char.class, boolean.class, byte.class, or void.class.
  • .getTypeName() returns "int", "long", "short", "double", "float", "char", "boolean", "byte" or "void", matching Class#getTypeName().
  • .getTypeSignatureChar() returns the single character used to represent the type in a classfile-internal type signature: I, J, S, D, F, C, Z, B or V respectively.

ReferenceTypeSignature

A type signature for a reference type. Subclasses are ClassRefOrTypeVariableSignature and ArrayTypeSignature.

ArrayTypeSignature

A type signature for an array type.

  • .getElementTypeSignature() returns the type of the innermost nested element type of the array (e.g. for an array of type int[][][], this returns a BaseTypeSignature representing int.class).
  • .getNumDimensions() returns the number of dimensions of the array.
  • .getNestedType() returns a TypeSignature for the array class with one dimension fewer, or the innermost element type if there are no nested array dimensions.
  • .getTypeSignatureString() returns the raw classfile-internal array type signature string, e.g. "[[I" for int[][].
  • .getArrayClassInfo() returns an ArrayClassInfo for the array class.

ClassRefOrTypeVariableSignature

A type signature for a class reference or a type variable. Subclasses are ClassRefTypeSignature and TypeVariableSignature.

ClassRefTypeSignature

A type signature for a Class reference.

  • .getBaseClassName() returns the base name of the referenced class (without suffixes or type arguments).
  • .getFullyQualifiedClassName() returns the fully-qualified name of the referenced class (with suffixes, but without type arguments).
  • .getTypeArguments() returns the type arguments of the class reference as a List of TypeArgument objects.
  • .getSuffixes() returns the class suffixes (for inner classes).
  • .getSuffixTypeArguments() returns the type arguments of the class reference as a List of Lists of TypeArgument objects, one list for each suffix.
  • .getSuffixTypeAnnotationInfo() returns a List of AnnotationInfoList elements, one for each suffix, consisting of the type annotations of that suffix, or null if there are no suffix type annotations. Note that the type annotation on the base class is obtained by calling the superclass method TypeSignature#getTypeAnnotationInfo().
  • .getClassInfo() returns the ClassInfo object for the referenced class, if the referenced class was encountered during scanning (causing a ClassInfo object to be created for it). Returns null if the referenced class was not encountered during scanning, e.g. if it was rejected -- call .getFullyQualifiedClassName() if you need the class name in that case.

TypeVariableSignature

A type signature for a type variable.

  • .getName() returns the name of the type variable (e.g. "T") as a String.
  • .resolve() looks up a type variable (e.g. T) in the defining method (or if that fails, in the enclosing class), and returns the TypeParameter with the same name (e.g. T extends com.xyz.Cls). If neither the method nor the class declares a type parameter of that name, an unbounded TypeParameter with just the type variable's name is returned. Throws IllegalStateException if the enclosing class was not found during the scan.
  • .toStringWithTypeBound() returns the type variable along with its type bound, if available (e.g. "T extends com.xyz.Cls"), or just the type variable if it has no bound or the bound could not be determined. This is the string form of what .resolve() returns.

Additional type-related classes

TypeArgument

A (possibly-wildcarded) generic type argument.

  • .getWildcard() returns a Wildcard enum value, which can be one of the values NONE, ANY (i.e. ? in Java syntax), EXTENDS and SUPER.
  • .getTypeSignature() returns a ReferenceTypeSignature for the type bounded by the wildcard.

TypeParameter

A generic type parameter.

  • .getName() returns the type parameter identifier (e.g. "T") as a String.
  • .getClassBound() returns the type parameter class bound as a ReferenceTypeSignature. May be null if there is no class bound.
  • .getInterfaceBounds() returns the type parameter interface bounds as a List of ReferenceTypeSignature objects. Returns the empty list if there are no interface bounds.

Clone this wiki locally