58

I am using some third party code which when given a '-classpath' command line argument doesnt set the java.class.path, but instead just creates a classloader, adds all the urls for the items on the command line specified classpath to the classloader, and then sets it to be the context classloader. In a plugin class to this code that I have written, I get an instance of this classloader, and somehow need to use it to get back the underlying classpath, so that I can use it in an invocation of JavaCompiler.getTask(...) and compile some other code on the fly. However there doesn't seem to be anyway to get the ClassPath from the ClassLoader, and as java.class.path is unset, I can't seem to access the underlying classpath that the application was initially invoked with...Any ideas?

Marcus Mathioudakis
  • 837
  • 1
  • 6
  • 19

5 Answers5

65

If the classloader uses URLs, it must be a URLClassloader. What you have access to is the URLs which defines the classpath for it along side with its parent ClassLoader.

To get the URLs, simply do the following:

((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()
guido
  • 18,864
  • 6
  • 70
  • 95
Adel Boutros
  • 10,205
  • 7
  • 55
  • 89
  • 11
    This solution doesn't work anymore on Java 9, because the class loader can not be casted into URLClassLoader – G Quintana Mar 15 '18 at 20:34
  • 1
    G Quintana is correct. See my answer on scanning the Java 9+ module path here: https://stackoverflow.com/questions/41932635/scanning-classpath-modulepath-in-runtime-in-java-9/45612376#45612376 – Luke Hutchison Jul 05 '18 at 23:43
  • 2
    ClassCastException: Cannot cast 'jdk.internal.loader.ClassLoaders$AppClassLoader' to 'java.net.URLClassLoader' – cdalxndr Feb 03 '21 at 17:13
37

The cleanest way to enumerate the classpath today is to use the ClassGraph library (I am the author). Note that the old answer of reading the java.class.path property or calling ((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs() is woefully inadequate if you want your code to be portable today, because numerous runtime environments no longer use java.class.path, and/or their classloaders don't extend URLClassLoader, and/or they use some obscure mechanism for extending the classpath (like the Class-Path: property in a jar's manifest file), and/or your code may be run as a module in JDK 9+ (or your code will be run on the traditional classpath in JDK9+, but the standard JDK classloaders for the traditional classpath don't even extend URLClassLoader anymore).

ClassGraph handles an enormous number of classpath specification mechanisms and classloader implementations automatically. For most of the supported classloaders, custom reflection code has been written for ClassGraph to obtain the classpath from the classloader (this is required since the ClassLoader API does not have any standard mechanism for obtaining the classpath). You could write your own code for this, but probably it will only support URLClassLoader without expending significant effort -- so it is probably better to just use ClassGraph, since the work is already done for you.

To get the classpath (and non-system modular jars added to the module path), just call:

List<URI> classpath = new ClassGraph().getClasspathURIs();

Note that in Java 9+, modules (or jlink'd jars) may appear in the list with jrt: URIs, which you can't do much with directly (other than use ClassGraph to read resources and classes from them, since ClassGraph can additionally use the JPMS API to access these resources and classes). You can also use ClassGraph to enumerate or scan all classes and/or all resources in the classpath (see the ClassGraph wiki).

In a modular project in Java 9+, you may also want to obtain a list of ModuleReference objects for visible modules in the system. These can be obtained by calling the following (ModuleRef is a wrapper for ModuleReference that is backwards compatible, so you can compile your code on JDK 7/8 but still take advantage of module features on JDK 9+):

List<ModuleRef> modules =
    new ClassGraph()
        .enableSystemPackages() // Optional, to return system modules
        .getModules();

Or you can get the actual module path passed into the commandline (--module-path, --patch-module, --add-exports etc.) by calling the following, returning a list of ModulePathInfo objects:

List<ModulePathInfo> modulePathInfo = new ClassGraph().getModulePathInfo();
Luke Hutchison
  • 8,186
  • 2
  • 45
  • 40
  • 2
    Definitely the most complete answer and actively evolving if you follow the project. – Michael McCallum Aug 21 '15 at 10:34
  • 1
    That dirty method that looks for any other classloaders on the call stack was exactly what I needed! Thanks so much! – FelipeKunzler Feb 19 '16 at 19:27
  • 2
    @luke-hutchison your library is a life saver! For anyone wanting a solution that gets classpath for a variety of runtime environments this is the best out there. – Dan Feb 26 '16 at 02:49
  • I have compared the output of `ClassGraph` and `URLClassloader.getURLs()` for a Spring-boot application with embedded Tomcat. ClassGraph ignores the `/BOOT-INF/classes` folder and also some jar in `/lib/ext`, while `getURLs()` ignores a `tomcat-docbase.nnn` folder created in my system temp folder. – Pino Nov 25 '20 at 15:02
  • @Pino Both of those directories have explicit support in ClassGraph, so they should work. Can you please file a bug report at the ClassGraph github site? – Luke Hutchison Nov 27 '20 at 06:04
  • To Luke's point about ClassLoader types in different contexts: `THREAD CONTEXT CLASSLOADER TYPE [org.akhikhl.gretty.TomcatEmbeddedWebappClassLoader]` – John Blum Sep 16 '22 at 19:15
  • While the output of the `ClassLoader` type I posted above is telling, FTR I am running a Gradle based build using the Gretty Plugin to bootstrap a Tomcat Webserver during integration testing for a Spring Session Web app. – John Blum Sep 16 '22 at 19:18
  • `org.akhikhl.gretty.TomcatEmbeddedWebappClassLoader` is not a `java.net.URLClassLoader`. – John Blum Sep 16 '22 at 19:22
  • 1
    Also, FTR, ClassGraph worked like a champ in my UC. Thank you Luke! – John Blum Sep 16 '22 at 20:08
10

For future reference, in case you need to pass in the class path to ProcessBuilder:

StringBuffer buffer = new StringBuffer();
for (URL url :
    ((URLClassLoader) (Thread.currentThread()
        .getContextClassLoader())).getURLs()) {
  buffer.append(new File(url.getPath()));
  buffer.append(System.getProperty("path.separator"));
}
String classpath = buffer.toString();
int toIndex = classpath
    .lastIndexOf(System.getProperty("path.separator"));
classpath = classpath.substring(0, toIndex);
ProcessBuilder builder = new ProcessBuilder("java",
    "-classpath", classpath, "com.a.b.c.TestProgram");
BJ Dela Cruz
  • 5,194
  • 13
  • 51
  • 84
6

In case other answers don't work, try this:

ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url: urls) {
    System.out.println(url.getFile());
}
Yavin5
  • 679
  • 10
  • 11
0

Drop this code into an empty jsp page to view classLoader hierarchy and associated jars loaded at each level.

visit() method below could also be used on its own

<%!
    public void visit(StringBuilder sb, int indent, ClassLoader classLoader) {
        if (indent > 20 || classLoader == null)
            return;
        String indentStr = new String(new char[indent]).replace("\0", "    ");
        sb.append("\n");
        sb.append(indentStr);
        sb.append(classLoader.getClass().getName());
        sb.append(":");
        if (classLoader instanceof java.net.URLClassLoader) {
            java.net.URL[] urls = ((java.net.URLClassLoader)classLoader).getURLs();
            for (java.net.URL url : urls) {
                sb.append("\n");
                sb.append(indentStr);
                sb.append(url);
            }
        }
        sb.append("\n");
        visit(sb, indent + 1, classLoader.getParent());
    }

%>

<%
StringBuilder sb = new StringBuilder();
visit(sb,1,this.getClass().getClassLoader());
%>
<pre>
<%=sb%>
</pre>
Prashant Bhate
  • 10,907
  • 7
  • 47
  • 82