ClassGraph: Inverting the Java Class and Reflection API
An uber-fast parallelized Java classpath scanner and module scanner.
At a glance
- What is it?
- ClassGraph is a parallelized classpath and module scanner for the JVM that indexes classes, annotations and resources without loading them. This review covers how the scan model works, how it is published on Maven Central, and where it stops being the right tool.
- Who is it for?
- Adopt ClassGraph when you need to enumerate subclasses, implementers, annotated classes or matching resources across the classpath and module path without initializing them, and when a no-dependency jar matters. Do not adopt it as a general-purpose reflection replacement or as a runtime dependency-discovery mechanism for code you cannot enumerate ahead of time.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The inversion problem ClassGraph was built to solve
Java's class and reflection API answers questions in one direction. Given a class, it tells you its superclass, its interfaces and its annotations. It cannot answer the reverse question: which classes extend this one, which implement that interface, which carry this annotation. The README frames this as the ability to invert the class and reflection API, or to index classes and resources. The same gap exists for resources. The Java API loads a resource at a known path from a known ClassLoader; it will not tell you which resources across all classloaders match a pattern.
That reverse lookup is the whole product. It matters to anyone building a plugin system, a DI container, a routing layer, a serializer registry or a test-discovery tool, where the set of candidate classes is not known at compile time. ClassGraph targets JVM languages generally, so Java, Scala and Kotlin projects are all in scope, and the README lists Windows, macOS, Linux and build-time Android as supported platforms.
How a ClassGraph scan actually works
The scan model is opt-in at every level. The README states plainly that nothing is scanned unless it is enabled, and that a scan with no enable method called finds nothing at all. You begin by declaring where to look: the classpath, the modules, or specific classloaders, module layers and classpath entries. The enable methods come in pairs, a no-argument form that scans what is in the environment and a varargs form that scans exactly what is named. enableClasspath() with no arguments covers every classpath element of every classloader that can be found, including the thread context classloader, the system classloader, the classloader of the class in any frame of the call stack, and the ancestors of all of those.
A second family of methods declares what to read. enableClassInfo() reads the classfiles, enableAnnotationInfo() reads the annotations on those classes. Only then does scan() run, returning a ScanResult that you close. The example in the README wraps the scan in a try-with-resources block, which is the intended lifecycle. Filtering happens through acceptPackages and acceptPathsNonRecursive, so the scan is narrowed before results are materialized rather than filtered afterwards in application code. The README notes the scan does not load or initialize the scanned classes, which is the property that makes the approach usable in environments where static initializers would have side effects.
Running a first ClassGraph scan
ClassGraph is published on Maven Central under the group and artifact io.github.classgraph:classgraph, and the README carries a Maven Central badge pointing at that coordinate. The README does not print a dependency snippet, so the version you declare is whichever release you pick from Maven Central; the recent releases listed for the repository include classgraph-4.8.196. The README advertises the library as having no dependencies, so nothing should be pulled in behind it.
For a first real use, take the annotation lookup from the README. It finds every class under com.xyz, or its subpackages, that carries @com.xyz.Route with a string parameter, and prints the route value:
String pkg = "com.xyz";
String routeAnnotation = pkg + ".Route";
try (ScanResult scanResult =
new ClassGraph()
.verbose() // Log to stderr
.enableNonSystemModules() // Scan the module path
.enableClasspath() // Scan the classpath
.enableClassInfo() // Read the classfiles
.enableAnnotationInfo() // ... and the annotations on the classes
.acceptPackages(pkg) // Scan com.xyz and subpackages (omit to scan all packages)
.scan()) { // Start the scan
for (ClassInfo routeClassInfo : scanResult.getClassesWithAnnotation(routeAnnotation)) {
AnnotationInfo routeAnnotationInfo = routeClassInfo.getAllAnnotationInfo(routeAnnotation);
List<AnnotationParameterValue> routeParamVals = routeAnnotationInfo.getParameterValues();
// @com.xyz.Route has one required parameter
String route = (String) routeParamVals.get(0).getValue();
System.out.println(routeClassInfo.getName() + " is annotated with route " + route);
}
}With verbose() enabled, ClassGraph logs to stderr, so you should see scan progress in the console before the printed class names. Remove verbose() once the scan behaves as expected. The second README example covers resources rather than classes: acceptPathsNonRecursive("META-INF/config") followed by getResourcesWithExtension("json") and forEachByteArray. The README warns that forEachByteArray throws IOException if a resource cannot be read, and that forEachByteArrayIgnoringIOException skips unreadable resources silently, so the enclosing method must declare the exception in the first case.
Where the opt-in model becomes a trap
The same design that keeps scans fast makes misconfiguration quiet. A missing enableClasspath() or enableAnnotationInfo() does not raise an error; it returns fewer results, and the failure looks like an application bug rather than a scanner configuration problem. This is the single most likely source of wasted debugging time for a new user, and the README addresses it only by stating the rule, not by providing a diagnostic for a scan that returns nothing.
The second constraint is the JDK baseline. The README's compatibility badge says JDK 17+ (JPMS), so projects pinned to older JDKs cannot use the current line, and the module-path features assume a JPMS-aware runtime. Third, ClassGraph is a scanner, not a class loader. It reports what exists and, in the resource case, hands you bytes. It does not resolve dependencies, instantiate the classes it finds, or manage their lifecycle. If your real need is dependency injection with wiring and scope, a scanner is one layer below that problem. Finally, the README does not document any rollback or downgrade procedure, so pinning a version and reading the release notes before moving is the only guidance the repository offers.
ClassGraph against the Reflections library
The comparison that shows up most often in search is ClassGraph versus Reflections. Both solve the same reverse-lookup problem, and both are classpath scanners, so the difference is in the scan model rather than the feature list. Reflections-style scanning typically builds a metadata store and queries it, which suits repeated queries against a stable classpath. ClassGraph makes the scan itself the unit of work: you declare the sources and the metadata you want, call scan(), and consume a ScanResult that you close. The README's emphasis on parallelized scanning and on not loading or initializing scanned classes points at the same trade-off, a scan tuned to be cheap enough to run on demand.
The practical consequence is in how you structure code. If your application queries the same classpath repeatedly and wants a long-lived index, the store model is a closer fit. If you scan once at startup, or scan a bounded package for a routing or plugin table, ClassGraph's per-scan lifecycle maps directly onto that. If you want the scan result rendered rather than queried, the repository also contains a classgraph-viz module alongside classgraph-base, classgraph-classpath and classgraph-vfs, so visualization is a sibling artifact rather than part of the core jar.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-23, the same day as the classgraph-4.8.196 release. Releases in the recent line have arrived at a steady cadence, with 4.8.194 on 2026-08-25, 4.8.195 on 2026-09-07 and 4.8.196 on 2026-09-23. The version numbering is worth noting for upgrade planning: the 4.8.x line has run to at least 196 patch releases, which suggests frequent small fixes rather than rare large ones. Teams that pin an exact version will therefore see many available updates and need a policy for choosing when to move.
ClassGraph is MIT licensed, with the licence text in LICENSE-ClassGraph.txt at the repository root. MIT is permissive and imposes no copyleft obligation on your application, but the README does not discuss attribution requirements or third-party notice files, so if your organisation has a notice-file process, confirm how the licence text should be carried. This is not legal advice; check with whoever handles licensing in your organisation. On the build side, the README's no-dependencies claim means an upgrade is a version bump with no transitive resolution to re-audit, which keeps the mechanical cost of staying current low.
Editorial conclusion
Adopt ClassGraph when you need to enumerate subclasses, implementers, annotated classes or matching resources across the classpath and module path without initializing them, and when a no-dependency jar matters. Do not adopt it as a general-purpose reflection replacement or as a runtime dependency-discovery mechanism for code you cannot enumerate ahead of time. Before committing, verify that your build resolves io.github.classgraph:classgraph from Maven Central, that your JDK matches the 17+ JPMS baseline if you scan the module path, and that you explicitly call the enable methods for every source you expect to be scanned, because a scan with no enable call finds nothing at all.
Frequently asked questions
How do I add ClassGraph to a Maven project?
Declare io.github.classgraph:classgraph as a dependency, taking the version from Maven Central. The README links to Maven Central under the io.github.classgraph group and advertises the library as having no dependencies.
What is ClassGraph used for in Java?
It indexes classes and resources so you can ask reverse questions the reflection API cannot answer, such as all classes that extend a given class, implement a given interface, or carry a given annotation. The README also covers finding all resources across classloaders whose paths match a pattern.
Does ClassGraph load the classes it scans?
The README states that the example scan is accomplished without loading or initializing any of the scanned classes. The scan reads classfile and annotation metadata and returns it through ScanResult.
Why does a ClassGraph scan return no results?
The README states that nothing is scanned unless it is enabled, and that a scan with no enable method called finds nothing at all. You need to enable at least one source, such as the classpath or the modules, plus the metadata you want, such as class info or annotation info.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/classgraph-classgraph)