Code examples
See also the ClassGraph API overview.
After instantiating a ClassGraph
instance, you configure the instance for scanning. Two of the main configuration steps are to call .enableAllInfo()
if you want to scan classfiles (without this, you will only get Resource
results), and to call .whitelistPackages(String... packageName)
to whitelist the packages you want to scan (without whitelisting packages, all packages are scanned). You can also call .verbose()
to log to stderr for debugging purposes.
You then call .scan()
to start the scan. This returns a ScanResult
instance, which you can query for classes of interest, producing a ClassInfoList
of ClassInfo
objects.
try (ScanResult scanResult = // Assign scanResult in try-with-resources
new ClassGraph() // Create a new ClassGraph instance
.verbose() // If you want to enable logging to stderr
.enableAllInfo() // Scan classes, methods, fields, annotations
.whitelistPackages("com.xyz") // Scan com.xyz and subpackages
.scan()) { // Perform the scan and return a ScanResult
ClassInfo widgetClassInfo = scanResult.getClassInfo("com.xyz.Widget");
// ...
}
💡 The
ScanResult
should be assigned in a try-with-resources block in Java, or used inside a.use {...}
block in Kotlin, or used inside a.withCloseable {...}
block in Groovy, or closed in afinally
block in Scala. This causes the resources associated with theScanResult
(including any temporary files created by unzipping inner jars) to be freed/deleted once theScanResult
is no longer needed. SeeScanResult
lifecycle.
From a ScanResult
, you can query for classes of a given type, producing a ClassInfoList
, consisting of ClassInfo
objects:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList routeClassInfoList = scanResult.getClassesWithAnnotation("com.xyz.Route");
for (ClassInfo routeClassInfo : routeClassInfoList) {
// Get the Route annotation on the class
AnnotationInfo annotationInfo = routeClassInfo.getAnnotationInfo("com.xyz.Route");
AnnotationParameterValueList annotationParamVals = annotationInfo.getParameterValues();
// The Route annotation has a parameter named "path"
String routePath = annotationParamVals.get("path");
// Alternatively, you can load and instantiate the annotation, so that the annotation
// methods can be called directly to get the annotation parameter values (this sets up
// an InvocationHandler to emulate the Route annotation instance, since annotations
// can't be instantiated directly)
Route route = (Route) annotationInfo.loadClassAndInstantiate();
String routePathDirect = route.path();
// ...
}
}
For convenience, you can get a list of class names from a ClassInfoList
by calling the .getNames()
method:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList widgetClasses = scanResult.getClassesImplementing("com.xyz.Widget");
List<String> widgetClassNames = widgetClasses.getNames();
// ...
}
You can get Class<?>
references from ClassInfo
objects by calling the .loadClass()
method, or from a ClassInfoList
by calling the .loadClasses()
method:
🛑 N.B. You should do all classloading through ClassGraph, using
ClassInfo#loadClass()
orClassInfoList#loadClasses()
, and never usingClass.forName(className)
, otherwise you may end up with some classes loaded by the context classloader, and some by another classloader (ClassGraph may need to create its own custom classloader to load classes from some jarfiles). This can causeClassCastException
or other problems at weird places in your code.
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList controlClasses = scanResult.getSubclasses("com.xyz.Control");
List<Class<?>> controlClassRefs = controlClasses.loadClasses();
// ...
}
From a ClassInfo
object, you can fetch a MethodInfoList
of MethodInfo
objects, a FieldInfoList
of FieldInfo
objects, and/or an AnnotationInfoList
of AnnotationInfo
objects, which respectively provide information about the methods, fields and annotations of the class.
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfo form = scanResult.getClassInfo("com.xyz.Form");
if (form != null) {
MethodInfoList formMethods = form.getMethodInfo();
for (MethodInfo mi : formMethods) {
String methodName = mi.getName();
MethodParameterInfo[] mpi = mi.getParameterInfo();
for (int i = 0; i < mpi.length; i++) {
String parameterName = mpi[i].getName();
TypeSignature parameterType =
mpi[i].getTypeSignatureOrTypeDescriptor();
// ...
}
}
FieldInfoList formFields = form.getFieldInfo();
for (FieldInfo fi : formFields) {
String fieldName = fi.getName();
TypeSignature fieldType = fi.getTypeSignatureOrTypeDescriptor();
// ...
}
AnnotationInfoList formAnnotations = form.getAnnotationInfo();
for (AnnotationInfo ai : formAnnotations) {
String annotationName = ai.getName();
List<AnnotationParameterValue> annotationParamVals =
ai.getParameterValues();
// ...
}
}
}
This is compound query that requires a Boolean "and" operator. There is more than one way to accomplish this:
ClassInfoList
provides union ("and"), intersection ("or"), and set difference / exclusion ("and-not") operators:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList boxes = scanResult.getSubclasses("com.xyz.Box");
ClassInfoList checked = scanResult.getClassesWithAnnotation("com.xyz.Checked");
ClassInfoList checkedBoxes = boxes.intersect(checked);
// ...
}
Equivalently, you could also apply a filter predicate to a ClassInfoList
to produce a new ClassInfoList
contanining a selected subset of elements:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList checkedBoxes = scanResult.getSubclasses("com.xyz.Box")
.filter(classInfo -> classInfo.hasAnnotation("com.xyz.Checked"));
// ...
}
Filter criteria can be arbitrarily complex, for example:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList filtered = scanResult.getAllClasses()
.filter(classInfo ->
(classInfo.isInterface() || classInfo.isAbstract())
&& classInfo.hasAnnotation("com.xyz.Widget")
&& classInfo.hasMethod("open"));
// ...
}
Note that some of the ClassInfo
predicate methods do not take a parameter, so they can also be used directly as function references in place of a ClassInfoFilter
, e.g.:
ClassInfoList interfaces = classInfoList.filter(ClassInfo::isInterface);
The ClassInfoList#directOnly()
method restricts a list of ClassInfo
objects to only those in direct relationship, given the relationship type that produced the ClassInfoList
. For example, to restrict the list of subclasses of com.xyz.Box
to just the direct subclasses, and not subclasses of the subclasses:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList directBoxes = scanResult.getSubclasses("com.xyz.Box").directOnly();
// ...
}
A meta-annotation annotates an annotation. For the ClassInfo
of an annotation class, ci
, the classes it annotates can be found by calling ci.getClassesWithAnnotation()
, returning a ClassInfoList
. That list can be filtered for only annotation classes by then calling .getAnnotations()
, returning the list of classes annotated by ci
that are themselves annotations. Checking if that list is empty tests whether or not ci
is a meta-annotation:
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.scan()) {
ClassInfoList metaAnnotations = scanResult.getAllAnnotations()
.filter(ci -> !ci.getClassesWithAnnotation().getAnnotations().isEmpty());
// ...
}
Rather than scanning all detected classloaders and modules, you can scan specific URLs by calling either .overrideClassLoaders(new URLClassLoader(urls))
or simply .overrideClasspath(urls)
before .scan()
:
public void scan(URL[] urls) {
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("com.xyz")
.overrideClassLoaders(new URLClassLoader(urls))
.scan()) {
// ...
}
}
This is a different type of query that finds resources with a matching file path, rather than classes based on class properties.
If you only need to scan resources and not classes, .enableClassInfo()
or .enableAllInfo()
should not be called, for speed. Also, if you don't need to scan classes, you should specify the whitelist by calling .whitelistPaths()
and using path separators (/
), rather than by calling .whitelistPackages()
and using package separators (.
). Path and package whitelists work the same way internally, you can just choose one way or the other of specifying the whitelist/blacklist. However, calling .whitelistPackages()
also implicitly calls .enableClassInfo()
.
Map<String, String> pathToFileContent = new HashMap<>();
try (ScanResult scanResult = new ClassGraph().whitelistPaths("META-INF/config").scan()) {
scanResult.getResourcesWithExtension("xml")
.forEachByteArray((Resource res, byte[] fileContent) -> {
pathToFileContent.put(
res.getPath(), new String(fileContent, StandardCharsets.UTF_8));
});
}
There are several methods in ScanResult
for getting resources matching given criteria:
.getAllResources()
.getResourcesWithPath(String resourcePath)
.getResourcesWithLeafName(String leafName)
.getResourcesWithExtension(String extension)
.getResourcesMatchingPattern(Pattern pattern)
It can be useful to know when the same class is defined multiple times in the classpath or module path.
In the ScanResult
, ClassGraph only returns one ClassInfo
object for any given fully-qualified class name, which corresponds to the first instance of the class encountered in the classpath or module path (any later definitions of the same class are ignored, in order to emulate the "masking" or "shadowing" semantics of the JRE). However, ScanResult#getAllResources()
returns a ResourceList
that includes Resource
objects for both non-classfiles and classfiles (since a classfile is technically a resource).
Calling ResourceList#classFilesOnly()
returns another ResourceList
consisting of only the Resource
elements whose path ends in ".class"
.
Calling ResourceList#findDuplicatePaths()
returns a List<Entry<String, ResourceList>>
where the entry key is the path, and the entry value is a ResourceList
consisting of two or more Resource
objects for the duplicated resources.
Therefore, you can print the classpath / module path URLs for all duplicated classfiles as follows:
for (Entry<String, ResourceList> dup :
new ClassGraph().scan()
.getAllResources()
.classFilesOnly() // Or remove for all resource types
.findDuplicatePaths()) {
System.out.println(dup.getKey()); // Classfile path
for (Resource res : dup.getValue()) {
System.out.println(" -> " + res.getURL()); // Resource URL showing classpath element
}
}