| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
The classgraph-classpath library works out where a running JVM loads its classes and resources from: the classpath elements, the module path, and the locations that a container's custom classloaders load from. It is the classpath finder that ClassGraph itself scans with, and it can be used on its own, without scanning anything. Replace X.Y.Z with the latest release number:
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph-classpath</artifactId>
<version>X.Y.Z</version>
</dependency>If you already depend on classgraph, you have this library too: classgraph depends on classgraph-classpath, which depends on classgraph-vfs, which depends on classgraph-base.
import io.github.classgraph.classpath.*;
try (Classpath classpath = new ClasspathFinder().enableClasspath().enableModules().find()) {
for (ClasspathEntry entry : classpath) {
System.out.println(entry.getLocation() + " (from " + entry.getClassLoaderName() + ")");
}
for (java.lang.module.ModuleReference module : classpath.getNonSystemModules()) {
System.out.println(module.descriptor().name());
}
}find() opens the jarfiles on the classpath, in order to read their manifests, so Classpath is AutoCloseable: closing it closes those jarfiles again, and deletes any temporary files that were needed to open a jarfile nested inside another jarfile. The classpath elements and the modules can still be read after close(), so if you only want the list of locations, you can close the Classpath immediately:
List<String> locations;
try (Classpath classpath = new ClasspathFinder().enableClasspath().enableModules().find()) {
locations = classpath.getLocations();
}Nothing is searched unless it is enabled, so a ClasspathFinder with no enable method called returns an empty Classpath. The methods that say where to search come in pairs. The method with no arguments enables the sources that are found in the current runtime environment; the method that takes varargs enables exactly the sources it is given:
| No arguments: search what is in the environment | Varargs: search exactly what is named |
|---|---|
| enableClasspath() -- every classpath element of every classloader that can be found -- 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, including java.class.path | enableClassLoaders(ClassLoader...), enableClasspathEntries(Object...) |
| enableModules(), enableSystemModules(), enableNonSystemModules() -- the modules of the module layers that are visible to the caller | enableModuleLayers(ModuleLayer...) |
Calling only the varargs method searches only what it names, and nothing from the environment. Calling both searches the environment as well as what you named. There is never a second overload of the same method name that changes the meaning of the call.
Each call adds to the end of the list of things to search, so classpath sources are reported in the order the calls were made. Modules always come first, whatever order they were enabled in, since that is the order in which the JVM resolves a class.
enableClasspath() finds every classpath element of every classloader that can be found in the environment. A classloader is found if it is any of the following:
Every classpath element that every one of those classloaders loads classes from is found, whether or not the classloader exposes it publicly -- see Classpath specification mechanisms for how each supported classloader is read. Elements are reported in the order in which the classloaders that declared them would be asked to load a class. The application classloader is normally one of the classloaders found, so the entries that the java.class.path system property lists are searched too, at the position the application classloader takes in that order.
The classpath elements that a classpath element declares are found as well, and added at the position they are reached from: the jarfiles in its automatic lib dirs, and the entries of its manifest's Class-Path and Bundle-ClassPath attributes. Each of those may declare more, so this is recursive. A jarfile, a jarfile nested inside another jarfile, and an exploded jarfile in a directory all declare their child classpath elements the same way, so a manifest is honoured wherever it is found. A child is resolved against the classpath element that declared it, so a relative Class-Path entry of a jarfile that lives in a non-default FileSystem names a sibling within that same filesystem.
A classpath element that is reached more than once is listed only at the first position it is reached at, which is the position that decides which copy of a duplicated class is loaded. Whether two elements are the same one is decided by their location, and a location that names a file or directory is canonical, so the same jarfile reached through a symbolic link and reached directly is one element rather than two.
A classloader may name a file or directory that is not there, or that cannot be read. Such an element is logged and left out of the classpath rather than listed, since it can contribute no class. An element that is there but cannot be opened -- a file with a .jar name that is not a zipfile, say -- is still listed, because whether it can be opened is only found out when it is opened.
An instance is not thread-safe while it is being configured, but find() may be called any number of times, and returns a new Classpath each time. The configuration methods return this, so they can be chained.
| Method | Effect |
|---|---|
| Classpath find() | Find the classpath elements and the modules. Throws IllegalStateException if the thread was interrupted while the jarfiles were being read. |
| ClasspathFinder verbose() | Log what is found to the io.github.classgraph.ClassGraph logger, at INFO level. For diagnosis only; not a stable output format. |
| ClasspathFinder enableURLScheme(String scheme) | Allow classpath elements to be named by a URL with the given scheme, e.g. "http". Every scheme the JVM has a handler for is allowed already, except http, https, ftp and mailto, which are denied to begin with because a classpath is not always something the caller wrote -- see custom URL schemes. An element whose scheme is denied is still reported, but is not opened, so the elements it declares are not found. Enabling a scheme also keeps a ':'-separated classpath string from being split at that scheme's own colon. |
| ClasspathFinder disableURLScheme(String scheme) | Deny a further URL scheme, so that a classpath element named by a URL with that scheme is reported but not opened. |
| Method | Effect |
|---|---|
| enableClasspath() | Search every classpath element of every classloader that can be found in the environment -- the thread context classloader, ClassGraph's own classloader, the system classloader, the classloader of the class in any frame of the call stack, and the ancestors of all of those -- including the java.class.path entries of the application classloader. See What is searched. |
| enableClasspathEntries(String classpath) | Search this classpath, with elements separated by File.pathSeparatorChar. No classloader is asked for it, so nothing else is searched unless it is enabled as well. |
| enableClasspathEntries(Object... elements) | The same, but each argument is one classpath entry and is not split on File.pathSeparatorChar. A String, File, Path, URL or URI is kept in that form, and is what the entry is later opened with; an element of any other type is read by its toString(). Throws IllegalArgumentException if any element is a ClassLoader -- pass those to enableClassLoaders() instead. |
| enableClasspathEntries(Iterable<?> elements) | The same, for a collection. A single Path passed here is treated as one classpath entry, not as a sequence of its name elements. |
| enableClassLoaders(ClassLoader... classLoaders) | Search these classloaders, and their parents, rather than the ones found in the environment. Call enableClasspath() as well to search both. |
| ignoreParentClassLoaders() | Do not search the parents of the classloaders that are searched. Elements that only a parent declares are left out. |
| registerClassLoaderHandler(ClassLoaderHandler handler) | Teach this library how to read the classpath out of a classloader that it does not already know about. Registered handlers are offered each classloader before the built-in handlers are, so a registered handler can also override a built-in one. When more than one handler can handle the same classloader, only the handlers that name the most specific classloader class are used, except that a handler you registered is never dropped this way. See writing a ClassLoaderHandler for an example. |
| enableSystemModules() | Search the system modules (java.*, jdk.*, javafx.*, oracle.*) of the module layers visible from the caller: the layers of the classes on the call stack, and the boot layer. |
| enableNonSystemModules() | Search the non-system modules of those same layers. |
| enableModules() | Both of the above. |
| enableModuleLayers(ModuleLayer... layers) | Search the non-system modules of these module layers, and of their parent layers, rather than of the layers visible from the caller. Use this if the calling code does not itself run within the module layer whose modules are wanted. |
| Method | Returns |
|---|---|
| List<ClasspathEntry> getEntries() | The classpath elements, in the order the classloaders would search them, including the elements declared by jarfiles on the classpath. Unmodifiable. |
| List<String> getLocations() | The getLocation() of each entry, in the same order. Unmodifiable. |
| List<ModuleReference> getModules() | The modules that were searched, system modules first, each group in name order. Includes the automatic modules created for jars on the classpath. Empty unless a module source was enabled. |
| List<ModuleReference> getSystemModules() | The modules whose name starts with java., jdk., javafx. or oracle., in name order -- the ones that ship with the JDK. |
| List<ModuleReference> getNonSystemModules() | The modules other than those, in name order. |
| ModulePathInfo getModulePathInfo() | The module path switches this JVM was launched with. |
| Vfs getVfs() | The virtual filesystem that the jarfiles' manifests were read through, so that a classpath element can be read without opening it a second time. Pass it to ClasspathEntry#open(Vfs) to read a classpath element, or to Vfs#open(ModuleReference) to read a module. Closed by close(), so do not close it yourself, and do not let anything it hands out escape the block. |
| void close() | Close the jarfiles that were opened to read their manifests, and delete any temporary files. Calling it more than once has no further effect. |
A Classpath is Iterable<ClasspathEntry>, so a for-each loop over it iterates the same entries as getEntries(), in the same order. The modules are a separate list, and iterating a Classpath does not visit them.
toString() returns the classpath elements and then the modules, one per line.
Modules are reported as JDK ModuleReference objects: module.descriptor().name() is the module name, and module.location() is an Optional<URI> naming where it was loaded from.
One element of the classpath: a directory or a jarfile that classes and resources are loaded from.
| Method | Returns |
|---|---|
| String getLocation() | Where the element is. An absolute path for a local directory or jarfile, with / as the separator on every platform. A jarfile nested inside another jarfile is written in the Java outer.jar!/inner.jar form. Anything that is not a local file, e.g. an element served over HTTP by a container, is the URL or URI it was found as. A path is canonical, spelled the way the file is stored: symbolic links are resolved, and on a filesystem that ignores case, so is the case of each name. Two elements are the same element if their locations are equal, so a file that two classloaders reach by different paths is listed once, at the first position it is reached at. |
| String getClassLoaderName() | The toString() of the classloader this element was obtained from, or null if it did not come from a classloader -- for example an entry from java.class.path, or one named with enableClasspathEntries(). Only the string is kept, so that finding the classpath does not keep a classloader alive. |
| List<String> getPackageRootPrefixes() | The directory prefixes to look for within this element and strip if present, because a classloader of this type goes looking for the root of the package hierarchy below them, e.g. "WEB-INF/classes/" for a Tomcat webapp classloader. These are the layouts the classloader could have used, not the ones this element actually uses, so a prefix is listed whether or not a directory with that name is there. Empty for a classloader that loads classes only from the classpath elements it was given, which is almost all of them; never contains the empty string. |
| List<String> getLibDirPrefixes() | The lib dirs whose jarfiles are added to the classpath if they are present within this element, because a classloader of this type loads from them without listing them as classpath elements, e.g. "WEB-INF/lib/" for a Tomcat webapp classloader. As with getPackageRootPrefixes(), these are the lib dirs the classloader could load from, not the ones this element actually has. Empty for a classloader that lists every jarfile it loads from; never contains the empty string. |
| VfsRoot open(Vfs vfs) | Open the element for reading, and return a VfsRoot over its contents. Pass classpath.getVfs(), so that a jarfile that was already opened to read its manifest is not read a second time. Throws IOException if the element could not be opened or read. |
toString() is the location, followed by the classloader name in square brackets if there is one.
try (Classpath classpath = new ClasspathFinder().enableClasspath().enableModules().find()) {
Vfs vfs = classpath.getVfs();
for (ClasspathEntry entry : classpath) {
for (VfsEntry resource : entry.open(vfs)) {
System.out.println(resource.getPath());
}
}
}open(Vfs) opens the element in whichever form the classloader named it in, rather than flattening it to getLocation() and parsing that back into an object. That matters for the forms a location does not reliably round-trip: a Path in a filesystem other than the default one is read through that filesystem, whether or not its toUri() form resolves back to it and whatever URL schemes are denied, and a URL keeps the scheme it was found with. A network scheme still has to be allowed with new ClasspathFinder().enableClasspath().enableURLScheme("https") before find(), or open throws IOException.
ClasspathEntry is a sealed class, with one subclass per form a classloader can name an element in. Code that needs the object the element was found as can ask for it; code that only wants to read the element does not need to know which subclass it has.
| Subclass | Accessor | Found when |
|---|---|---|
| ClasspathEntry.OfPathString | -- | The element was named by a path string, which is the usual case, or by an object of a type this library does not recognize, whose toString() was taken as the location. |
| ClasspathEntry.OfFile | File getFile() | The element was named by a File. Spelled with getLocation(), so on Windows File#getPath() comes back with backslashes. |
| ClasspathEntry.OfPath | Path getPath() | The element was named by a Path. It may belong to a filesystem other than the default one, e.g. a jarfile mounted with FileSystems#newFileSystem, in which case getLocation() names it but nothing can be opened by that name. |
| ClasspathEntry.OfURL | URL getURL() | The element was named by a URL, or by a URI or path string with a scheme that had to be parsed as a URL. |
| ClasspathEntry.OfURI | URI getURI() | The element was named by a URI that did not have to be parsed as a URL. |
Because the type is sealed, those five are the only possibilities, so a switch over them is checked for exhaustiveness on Java 21 and later:
if (entry instanceof ClasspathEntry.OfURL urlEntry) {
System.out.println("Served over " + urlEntry.getURL().getProtocol());
}Two entries are equal if they were found in the same form, at the same location, from the same classloader, with the same package root prefixes -- so the same jarfile named by a File in one classpath and by a Path in another gives two entries that are not equal. Within a single classpath the question does not arise: an element is listed once, at the first position it is reached at, whichever form it was named in there, since duplicates are recognized by location.
The module system switches the JVM was launched with, read from the runtime's own commandline arguments. This covers only what was named on the commandline -- it does not include the traditional classpath, or the system modules, which the runtime adds by itself.
Each getter returns an unmodifiable, insertion-ordered Set of the switch's values, in the order they were given on the commandline. A switch that was not used gives an empty set, and a switch that was used more than once contributes all of its values to the same set.
| Method | Switch | One entry looks like |
|---|---|---|
| Set<String> getModulePath() | --module-path / -p | one module path element (the switch value is split on File.pathSeparatorChar) |
| Set<String> getAddModules() | --add-modules | a module name (the switch value is split on ,). ALL-DEFAULT, ALL-SYSTEM and ALL-MODULE-PATH are valid names here; see JEP 261 |
| Set<String> getPatchModules() | --patch-module | <module>=<file> |
| Set<String> getAddExports() | --add-exports | <source-module>/<package>=<target-module>(,<target-module>)* |
| Set<String> getAddOpens() | --add-opens | <source-module>/<package>=<target-module>(,<target-module>)* |
| Set<String> getAddReads() | --add-reads | <source-module>=<target-module> |
getAddExports() and getAddOpens() also include any Add-Exports and Add-Opens entries found in the manifests of jarfiles that were scanned, in the form <source-module>/<package>=ALL-UNNAMED.
| Back | FazBrowse Home | New Git URL |