The uber-fast, ultra-lightweight classpath and module scanner for JVM languages.
—
ClassGraph provides comprehensive support for Java's type system, including full generic type information, type parameters, wildcards, and complex nested generic types. This system allows precise analysis of generic types without requiring class loading.
import io.github.classgraph.TypeSignature;
import io.github.classgraph.BaseTypeSignature;
import io.github.classgraph.ClassRefTypeSignature;
import io.github.classgraph.ArrayTypeSignature;
import io.github.classgraph.TypeVariableSignature;
import io.github.classgraph.ClassTypeSignature;
import io.github.classgraph.MethodTypeSignature;
import io.github.classgraph.TypeParameter;
import io.github.classgraph.TypeArgument;
import java.util.List;All type signatures extend from the abstract TypeSignature base class:
TypeSignature typeSignature = fieldInfo.getTypeSignature();
// Type annotations (Java 8+)
AnnotationInfoList typeAnnotations = typeSignature.getTypeAnnotationInfo();
// Equality comparison ignoring type parameters
boolean equalIgnoringGenerics = typeSignature.equalsIgnoringTypeParams(otherSignature);
// String representation
String typeString = typeSignature.toString();Represents primitive Java types (int, long, boolean, etc.):
BaseTypeSignature primitiveType = (BaseTypeSignature) fieldInfo.getTypeSignature();
// Single character type code ('I' for int, 'J' for long, etc.)
char typeChar = primitiveType.getTypeSignatureChar();
// Human-readable type name
String typeName = primitiveType.getTypeStr(); // "int", "long", "boolean"
// Get primitive Class object
Class<?> primitiveClass = primitiveType.getType(); // int.class, long.class, etc.// Analyzing primitive fields
FieldInfoList fields = classInfo.getDeclaredFieldInfo();
for (FieldInfo field : fields) {
TypeSignature type = field.getTypeSignature();
if (type instanceof BaseTypeSignature) {
BaseTypeSignature primitive = (BaseTypeSignature) type;
System.out.println("Field " + field.getName() + " is primitive type: " + primitive.getTypeStr());
}
}Represents references to classes and interfaces, including generic types:
ClassRefTypeSignature classRef = (ClassRefTypeSignature) fieldInfo.getTypeSignature();
// Basic class information
String baseClassName = classRef.getBaseClassName(); // "java.util.List"
String fullyQualifiedName = classRef.getFullyQualifiedClassName(); // "java.util.List"
// Generic type arguments
List<TypeArgument> typeArguments = classRef.getTypeArguments();
// Inner class information
List<String> suffixes = classRef.getSuffixes(); // ["Inner", "Nested"]
List<List<TypeArgument>> suffixTypeArguments = classRef.getSuffixTypeArguments();
// Load referenced class
Class<?> referencedClass = classRef.loadClass(false); // Don't initialize
Class<?> referencedClassThrow = classRef.loadClass(); // Throw on error
// Get ClassInfo for referenced class
ClassInfo referencedClassInfo = classRef.getClassInfo();// Analyzing generic field: List<String>
FieldInfo listField = classInfo.getDeclaredFieldInfo("items");
if (listField.getTypeSignature() instanceof ClassRefTypeSignature) {
ClassRefTypeSignature listType = (ClassRefTypeSignature) listField.getTypeSignature();
System.out.println("Base type: " + listType.getBaseClassName()); // "java.util.List"
List<TypeArgument> typeArgs = listType.getTypeArguments();
if (!typeArgs.isEmpty()) {
TypeArgument firstArg = typeArgs.get(0);
System.out.println("First type argument: " + firstArg.getTypeSignature()); // "java.lang.String"
}
}
// Complex generic type: Map<String, List<User>>
FieldInfo mapField = classInfo.getDeclaredFieldInfo("userGroups");
ClassRefTypeSignature mapType = (ClassRefTypeSignature) mapField.getTypeSignature();
List<TypeArgument> mapArgs = mapType.getTypeArguments();
if (mapArgs.size() == 2) {
TypeSignature keyType = mapArgs.get(0).getTypeSignature(); // String
TypeSignature valueType = mapArgs.get(1).getTypeSignature(); // List<User>
if (valueType instanceof ClassRefTypeSignature) {
ClassRefTypeSignature listValueType = (ClassRefTypeSignature) valueType;
List<TypeArgument> listArgs = listValueType.getTypeArguments();
if (!listArgs.isEmpty()) {
TypeSignature userType = listArgs.get(0).getTypeSignature(); // User
System.out.println("User type: " + userType);
}
}
}Represents array types with full dimension information:
ArrayTypeSignature arrayType = (ArrayTypeSignature) fieldInfo.getTypeSignature();
// Full array type signature string
String arraySignature = arrayType.getTypeSignatureStr(); // "[Ljava/lang/String;"
// Element type information
TypeSignature elementType = arrayType.getElementTypeSignature(); // String
int dimensions = arrayType.getNumDimensions(); // 1 for String[], 2 for String[][]
// Nested type for multi-dimensional arrays
TypeSignature nestedType = arrayType.getNestedType();
// Array class information
ArrayClassInfo arrayClassInfo = arrayType.getArrayClassInfo();
// Load element and array classes
Class<?> elementClass = arrayType.loadElementClass(false);
Class<?> elementClassThrow = arrayType.loadElementClass();
Class<?> arrayClass = arrayType.loadClass(false);
Class<?> arrayClassThrow = arrayType.loadClass();// Analyzing various array types
FieldInfoList fields = classInfo.getDeclaredFieldInfo();
for (FieldInfo field : fields) {
TypeSignature type = field.getTypeSignature();
if (type instanceof ArrayTypeSignature) {
ArrayTypeSignature arrayType = (ArrayTypeSignature) type;
System.out.println("Field: " + field.getName());
System.out.println(" Dimensions: " + arrayType.getNumDimensions());
System.out.println(" Element type: " + arrayType.getElementTypeSignature());
System.out.println(" Full signature: " + arrayType.getTypeSignatureStr());
}
}
// Example outputs:
// Field: names
// Dimensions: 1
// Element type: java.lang.String
// Full signature: [Ljava/lang/String;
// Field: matrix
// Dimensions: 2
// Element type: int
// Full signature: [[IRepresents generic type variables (T, E, K, V, etc.):
TypeVariableSignature typeVar = (TypeVariableSignature) signature;
// Type variable name
String name = typeVar.getName(); // "T", "E", "K", "V", etc.
// Resolve to type parameter definition
TypeParameter typeParam = typeVar.resolve();
// String representation with bounds
String withBounds = typeVar.toStringWithTypeBound(); // "T extends Number"// Analyzing generic class: class Container<T extends Number>
ClassTypeSignature classSignature = classInfo.getTypeSignature();
if (classSignature != null) {
List<TypeParameter> typeParams = classSignature.getTypeParameters();
for (TypeParameter param : typeParams) {
String paramName = param.getName(); // "T"
ReferenceTypeSignature classBound = param.getClassBound(); // Number
List<ReferenceTypeSignature> interfaceBounds = param.getInterfaceBounds();
System.out.println("Type parameter: " + paramName);
if (classBound != null) {
System.out.println(" Class bound: " + classBound);
}
for (ReferenceTypeSignature bound : interfaceBounds) {
System.out.println(" Interface bound: " + bound);
}
}
}
// Analyzing generic method: <T extends Comparable<T>> T max(T a, T b)
MethodInfo genericMethod = classInfo.getDeclaredMethodInfo("max").get(0);
MethodTypeSignature methodSignature = genericMethod.getTypeSignature();
if (methodSignature != null) {
List<TypeParameter> methodTypeParams = methodSignature.getTypeParameters();
TypeSignature returnType = methodSignature.getResultType();
List<TypeSignature> paramTypes = methodSignature.getParameterTypeSignatures();
}Represents complete class signatures including generics:
ClassTypeSignature classSignature = classInfo.getTypeSignature();
if (classSignature != null) {
// Generic type parameters of the class
List<TypeParameter> typeParameters = classSignature.getTypeParameters();
// Superclass with generic information
ClassRefTypeSignature superclassSignature = classSignature.getSuperclassSignature();
// Implemented interfaces with generic information
List<ClassRefTypeSignature> superinterfaceSignatures = classSignature.getSuperinterfaceSignatures();
}// Analyzing: class MyList<T extends Number> extends AbstractList<T> implements Serializable
ClassTypeSignature signature = classInfo.getTypeSignature();
// Type parameters: <T extends Number>
List<TypeParameter> typeParams = signature.getTypeParameters();
if (!typeParams.isEmpty()) {
TypeParameter tParam = typeParams.get(0);
System.out.println("Type parameter: " + tParam.getName()); // "T"
System.out.println("Class bound: " + tParam.getClassBound()); // Number
}
// Superclass: AbstractList<T>
ClassRefTypeSignature superclass = signature.getSuperclassSignature();
if (superclass != null) {
System.out.println("Superclass: " + superclass.getBaseClassName()); // AbstractList
List<TypeArgument> superArgs = superclass.getTypeArguments();
if (!superArgs.isEmpty()) {
System.out.println("Super type arg: " + superArgs.get(0).getTypeSignature()); // T
}
}
// Interfaces: Serializable
List<ClassRefTypeSignature> interfaces = signature.getSuperinterfaceSignatures();
for (ClassRefTypeSignature iface : interfaces) {
System.out.println("Interface: " + iface.getBaseClassName()); // Serializable
}Represents complete method signatures including generics:
MethodTypeSignature methodSignature = methodInfo.getTypeSignature();
if (methodSignature != null) {
// Method-level type parameters
List<TypeParameter> typeParameters = methodSignature.getTypeParameters();
// Parameter types
List<TypeSignature> parameterTypes = methodSignature.getParameterTypeSignatures();
// Return type
TypeSignature returnType = methodSignature.getResultType();
// Thrown exceptions
List<ClassRefOrTypeVariableSignature> thrownSignatures = methodSignature.getThrowsSignatures();
// Receiver type annotations (for inner class methods)
AnnotationInfoList receiverAnnotations = methodSignature.getReceiverTypeAnnotationInfo();
}// Analyzing: public <T extends Comparable<T>> List<T> sort(Collection<? extends T> items) throws IllegalArgumentException
MethodInfo sortMethod = classInfo.getDeclaredMethodInfo("sort").get(0);
MethodTypeSignature methodSig = sortMethod.getTypeSignature();
// Method type parameters: <T extends Comparable<T>>
List<TypeParameter> methodTypeParams = methodSig.getTypeParameters();
if (!methodTypeParams.isEmpty()) {
TypeParameter tParam = methodTypeParams.get(0);
System.out.println("Method type parameter: " + tParam.getName()); // "T"
List<ReferenceTypeSignature> bounds = tParam.getInterfaceBounds();
for (ReferenceTypeSignature bound : bounds) {
System.out.println(" Bound: " + bound); // Comparable<T>
}
}
// Return type: List<T>
TypeSignature returnType = methodSig.getResultType();
System.out.println("Return type: " + returnType);
// Parameter types: Collection<? extends T>
List<TypeSignature> paramTypes = methodSig.getParameterTypeSignatures();
for (TypeSignature paramType : paramTypes) {
System.out.println("Parameter type: " + paramType);
}
// Thrown exceptions: IllegalArgumentException
List<ClassRefOrTypeVariableSignature> thrownTypes = methodSig.getThrowsSignatures();
for (ClassRefOrTypeVariableSignature thrown : thrownTypes) {
System.out.println("Throws: " + thrown);
}// Analyzing generic types with wildcards: List<? extends Number>
ClassRefTypeSignature listType = (ClassRefTypeSignature) fieldInfo.getTypeSignature();
List<TypeArgument> typeArgs = listType.getTypeArguments();
for (TypeArgument arg : typeArgs) {
// Wildcard information
Wildcard wildcard = arg.getWildcard(); // NONE, ANY, EXTENDS, or SUPER
// Actual type signature
ReferenceTypeSignature typeSignature = arg.getTypeSignature();
switch (wildcard) {
case NONE:
System.out.println("Concrete type: " + typeSignature); // List<String>
break;
case ANY:
System.out.println("Unbounded wildcard: ?"); // List<?>
break;
case EXTENDS:
System.out.println("Upper bounded: ? extends " + typeSignature); // List<? extends Number>
break;
case SUPER:
System.out.println("Lower bounded: ? super " + typeSignature); // List<? super Integer>
break;
}
}// Analyzing bounded type parameters: <T extends Number & Comparable<T>>
ClassTypeSignature classSignature = classInfo.getTypeSignature();
List<TypeParameter> typeParams = classSignature.getTypeParameters();
for (TypeParameter param : typeParams) {
String name = param.getName(); // "T"
// Class bound (extends clause)
ReferenceTypeSignature classBound = param.getClassBound(); // Number
// Interface bounds (multiple interfaces after &)
List<ReferenceTypeSignature> interfaceBounds = param.getInterfaceBounds(); // [Comparable<T>]
System.out.println("Type parameter: " + name);
if (classBound != null) {
System.out.println(" Class bound: " + classBound);
}
for (ReferenceTypeSignature bound : interfaceBounds) {
System.out.println(" Interface bound: " + bound);
}
}// Analyzing complex nested generics: Map<String, List<Map<Integer, Set<User>>>>
FieldInfo complexField = classInfo.getDeclaredFieldInfo("complexData");
ClassRefTypeSignature mapType = (ClassRefTypeSignature) complexField.getTypeSignature();
System.out.println("Base type: " + mapType.getBaseClassName()); // Map
List<TypeArgument> mapArgs = mapType.getTypeArguments();
TypeSignature keyType = mapArgs.get(0).getTypeSignature(); // String
TypeSignature valueType = mapArgs.get(1).getTypeSignature(); // List<Map<Integer, Set<User>>>
if (valueType instanceof ClassRefTypeSignature) {
ClassRefTypeSignature listType = (ClassRefTypeSignature) valueType;
System.out.println("Value list type: " + listType.getBaseClassName()); // List
List<TypeArgument> listArgs = listType.getTypeArguments();
TypeSignature listElementType = listArgs.get(0).getTypeSignature(); // Map<Integer, Set<User>>
if (listElementType instanceof ClassRefTypeSignature) {
ClassRefTypeSignature innerMapType = (ClassRefTypeSignature) listElementType;
System.out.println("Inner map type: " + innerMapType.getBaseClassName()); // Map
List<TypeArgument> innerMapArgs = innerMapType.getTypeArguments();
TypeSignature innerKeyType = innerMapArgs.get(0).getTypeSignature(); // Integer
TypeSignature innerValueType = innerMapArgs.get(1).getTypeSignature(); // Set<User>
// Continue drilling down...
}
}// Analyzing: <T extends Number & Comparable<T> & Serializable> T process(T input)
MethodInfo processMethod = classInfo.getDeclaredMethodInfo("process").get(0);
MethodTypeSignature methodSig = processMethod.getTypeSignature();
List<TypeParameter> typeParams = methodSig.getTypeParameters();
TypeParameter tParam = typeParams.get(0);
System.out.println("Type parameter: " + tParam.getName()); // "T"
// Class bound
ReferenceTypeSignature classBound = tParam.getClassBound();
if (classBound != null) {
System.out.println("Class bound: " + classBound); // Number
}
// Interface bounds
List<ReferenceTypeSignature> interfaceBounds = tParam.getInterfaceBounds();
for (ReferenceTypeSignature bound : interfaceBounds) {
System.out.println("Interface bound: " + bound); // Comparable<T>, Serializable
}
// Return type (should be T)
TypeSignature returnType = methodSig.getResultType();
if (returnType instanceof TypeVariableSignature) {
TypeVariableSignature returnTypeVar = (TypeVariableSignature) returnType;
System.out.println("Return type variable: " + returnTypeVar.getName()); // "T"
}// Find all repository classes and analyze their generic types
ClassInfoList repositories = scanResult.getClassesImplementing("com.example.Repository");
for (ClassInfo repo : repositories) {
ClassTypeSignature repoSignature = repo.getTypeSignature();
if (repoSignature != null) {
// Get implemented interfaces to find Repository<EntityType, IdType>
List<ClassRefTypeSignature> interfaces = repoSignature.getSuperinterfaceSignatures();
for (ClassRefTypeSignature iface : interfaces) {
if ("com.example.Repository".equals(iface.getBaseClassName())) {
List<TypeArgument> args = iface.getTypeArguments();
if (args.size() == 2) {
TypeSignature entityType = args.get(0).getTypeSignature();
TypeSignature idType = args.get(1).getTypeSignature();
System.out.println("Repository: " + repo.getName());
System.out.println(" Entity type: " + entityType);
System.out.println(" ID type: " + idType);
}
}
}
}
}// Analyze service methods for generic return types
ClassInfoList services = scanResult.getClassesWithAnnotation("org.springframework.stereotype.Service");
for (ClassInfo service : services) {
MethodInfoList publicMethods = service.getDeclaredMethodInfo()
.filter(method -> method.isPublic() && !method.isStatic());
for (MethodInfo method : publicMethods) {
MethodTypeSignature methodSig = method.getTypeSignature();
if (methodSig != null) {
TypeSignature returnType = methodSig.getResultType();
// Look for generic collection return types
if (returnType instanceof ClassRefTypeSignature) {
ClassRefTypeSignature classReturn = (ClassRefTypeSignature) returnType;
String returnClassName = classReturn.getBaseClassName();
if (returnClassName.equals("java.util.List") ||
returnClassName.equals("java.util.Set") ||
returnClassName.equals("java.util.Optional")) {
List<TypeArgument> typeArgs = classReturn.getTypeArguments();
if (!typeArgs.isEmpty()) {
TypeSignature elementType = typeArgs.get(0).getTypeSignature();
System.out.println(service.getName() + "." + method.getName() +
" returns " + returnClassName + "<" + elementType + ">");
}
}
}
}
}
}The type signature system in ClassGraph provides complete access to Java's complex generic type system, enabling sophisticated analysis of generic types, bounds, and relationships without requiring class loading or runtime reflection.
Install with Tessl CLI
npx tessl i tessl/maven-io-github-classgraph--classgraph