Skip to content
Luke Hutchison edited this page Oct 8, 2021 · 277 revisions

ClassGraph Logo      Duke Award Logo

ClassGraph (formerly known as FastClasspathScanner) is a fast classpath scanner and module scanner for JVM languages, enabling lightweight metaprogramming in JVM languages: the ability to write code that analyzes or responds to the properties of other code.

Contents

ClassGraph API

Downloading/Building

See the GitHub project homepage for downloading/building instructions.

Running on JDK 16+

The JDK team decided to start enforcing strong encapsulation in JDK 16+. That will means that by default, ClassGraph will not be able to find the classpath of your project, if all of the following are true:

  • You are running on JDK 16+
  • You are using a legacy classloader (rather than the module system)
  • The legacy classloader does not expose its classpath via a public field or method
  • The classloader is loaded in a different module from your user code

If your ClassGraph code works in JDK versions less than 16 but breaks in JDK 16+ (meaning that ClassGraph can no longer find your classes), you have probably run into this problem.

ClassGraph can use either of the following libraries to silently circumvent all of Java's security mechanisms (visibility/access checks, security manager restrictions, and strong encapsulation), in order to read the classpath from private fields and methods of classloaders.

  • Narcissus by Luke Hutchison (@lukehutch), author of ClassGraph
  • JVM-Driver is by Roberto Gentili (@burningwave), author of Burningwave Core.

To use one of these libraries:

  • Upgrade ClassGraph to at least version 4.8.124
  • Either:
    1. Add the Narcissus library to your project as an extra dependency (this includes a native library, and only Linux x86/x64, Windows x86/x64, and Mac OS X x64 are currently supported -- feel free to contribute native code builds for other platforms or architectures).
    2. Set ClassGraph.CIRCUMVENT_ENCAPSULATION = CircumventEncapsulationMethod.NARCISSUS; before interacting with ClassGraph in any other way (this will load the Narcissus library as ClassGraph's reflection driver).
  • Or:
    1. Add the JVM-Driver library to your project as an extra dependency (this uses only Java code, but only works to circumvent encapsulation without native code in JDK 16 currently).
    2. Set ClassGraph.CIRCUMVENT_ENCAPSULATION = CircumventEncapsulationMethod.JVM_DRIVER; before interacting with ClassGraph in any other way (this will load the JVM-Driver library as ClassGraph's reflection driver).

JDK 16's strong encapsulation is just the first step of trying to lock down Java's internals, so further restrictions are possible (e.g. it is likely that setAccessible(true) will fail in future JDK releases, even within a module, and probably the JNI API will be locked down soon, making Narcissus require a commandline flag to work). Therefore, please convince your upstream runtime environment maintainers to expose the full classpath from their classloader using a public method or field, otherwise ClassGraph may stop working for your runtime environment in the future.

Use with maven-shade-plugin

If you use ClassGraph with maven-shade-plugin, you need to shade both io.github.classgraph. and nonapi.io.github.classgraph..

Use as a module

The ClassGraph jarfile includes a module-info.class file, which means that it can be added to your project as a Java module. In fact, in JDK 9+, if you are trying to call ClassGraph from code in a named module, ClassGraph must be added to the module path as a module, rather than included in the traditional classpath (you can't call non-modular code from modular code).

Requiring the ClassGraph module

To use ClassGraph as a Java module, add the jar dependency to your project's module path, then add the following to your module-info.java:

requires io.github.classgraph;

If when trying to run your code, you get the following exception, the problem is that the code you are trying to run is modular, but the ClassGraph jar was added to the regular classpath, not the module path, or vice versa:

java.lang.NoClassDefFoundError: io/github/classgraph/ClassGraph

In this case, if you manually added the ClassGraph jar to an Eclipse project, go to Project Properties > Java Build Path > Libraries > Classpath > classgraph-X.Y.Z.jar, open the dropdown, double click on Is not modular, and check the box at the top, Defines one or more modules, then OK, then Apply and Save.

If you are launching from the commandline, make sure both your project and ClassGraph are on the module path, not the classpath.

Opening your module to ClassGraph (scanning / loading classes in modules)

The Java Platform Module System (in JDK 9+) enforces strict encapsulation on both classes and resources. ClassGraph reads classfiles directly, so to some degree it can get around visibility restrictions, as long as the module exports the package that the classfile is contained within, since classfiles are just like any other files from the point of view of listing resources. If the classfile can be read, even if the class itself is non-public or its fields or methods are non-public, ClassGraph can still build ClassInfo, FieldInfo, MethodInfo objects etc., as long as you call ClassGraph#ignoreClassVisibility(), ClassGraph#ignoreFieldVisibility() and/or ClassGraph#ignoreMethodVisibility() before starting the scan.

However, if you try to actually load a non-public class found by ClassGraph in package that is exported but not opened to ClassGraph or to the world, by calling ClassInfo#loadClass() or ClassInfoList#loadClasses(), then the reflective call to instantiate the class will fail if the class and its superclasses are not public. (In other words, in general, listing and reading resources (including classfile scanning) only requires exports, but reflection (in particular, classloading) requires open/opens, for non-public classes and their superclasses.)

Note also that ClassGraph uses reflective access (often to private fields and/or methods) to get the classpath URLs and paths from ClassLoaders. This can give an "Illegal reflective access" warning on stderr, and if you are getting that warning, ClassGraph will not be able to read the necessary fields or call the necessary methods in some future JRE, once strong encapsulation is enforced. You will need to open your ClassLoader to ClassGraph to prevent this.

So the "tl;dr" version is:

  1. If you want ClassGraph to be able to scan classfiles or resources in a package, the package or the module must be exported, either to ClassGraph or to the world.
  2. If you are using ClassInfo#loadClass() or ClassInfoList#loadClasses() to get Class<?> references from ClassInfo objects, and the class and its superclasses are not all public, the package or the module must be opened, either to ClassGraph or to the world -- otherwise you will get an IllegalAccessError or similar.
  3. If you get "Illegal reflective access" warnings, you need to open the module containing your ClassLoader to ClassGraph.

Your options for opening the module or package to ClassGraph or to the world, given a module name of your.module, are:

  1. Open the entire module for reflection:
  open module your.module {
      requires io.github.classgraph;
  }
  1. Open only selected package(s) for reflection:
  module your.module {
      requires io.github.classgraph;
      opens some.package;
  }
  1. Open selected package(s) to ClassGraph specifically:
  module your.module {
      requires io.github.classgraph;
      opens some.package to io.github.classgraph;
  }

Use with a SecurityManager (advanced)

ClassGraph needs to obtain a stacktrace to find all ClassLoaders and/or modules. Most of the time, this will work fine out of the box. The exception is if you are using a SecurityManager.

If you are using a SecurityManager, then to enable ClassGraph to get a stacktrace:

  • In JDK 9+: you will need to grant to the classgraph module:
    • RuntimePermission("getStackWalkerWithClassReference")
    • RuntimePermission("accessClassInPackage.sun.misc")
    • RuntimePermission("accessClassInPackage.jdk.internal.misc")
    • ReflectPermission("suppressAccessChecks")
  • In JDK 7 or 8: you will need to grant to the classgraph jar:
    • RuntimePermission("createSecurityManager")
    • RuntimePermission("accessClassInPackage.sun.misc")
    • ReflectPermission("suppressAccessChecks")

There is a fallback methanism that will attempt to use Exception#getStackTrace() if the above permissions fail, but ClassGraph may fail to find the classpath or module path if the above permissions are not granted to runtime environments that do have a SecurityManager installed.