Welcome! Please see the About page for a little more info on how this works.

+3 votes
in java.classpath by

"What is my application's classpath?"

It turns out that there is not a straightforward answer to this question.

There are many ways of running Java, each of which may result in different definitions of the application's classpath. The clojure.java.classpath library has, historically, used a variety of heuristics to find and return the Java classpath.

The current approach is a workaround for Java 9, where the default classloader is not an instance of URLClassLoader. This is now causing problems for CIDER users when cider-jack-in injects the JDK source path (see below).

I have been looking at the history of clojure.java.classpath, in particular:

There does not seem to be a single approach that will give the right result in all situations.

  • Using the system property java.class.path produces incorrect results in an application container environment (CLASSPATH-1).
  • Getting the classpath from URLClassLoaders does not work in Java 9+ (CLASSPATH-8).
  • Combining the results would provide more coverage for tools like CIDER and tools.namespace, but would potentially return incorrect results for applications in containers.

I am unsure how to proceed. I am wary of making yet another change to address the most-recent issue, thereby breaking older uses.

See also, discussions at:

1 Answer

+1 vote
edited by

To expand on this slightly, the primary reason enumerating the classpath isn't straightforward is that Java has never defined a supported way to do it.

The system classloader, which generally loads the dependencies found on the java.class.path system property, is only guaranteed to be of type ClassLoader, which is not enumerable.

  • Until Java 9, it was possible to enumerate the system classloader by relying on an implementation detail: this reliably returned a URLClassLoader, which has a getURLs method. This was undocumented behavior, however.

  • Beginning with Java 9, the system classloader implementation was changed to an internal class that is not enumerable.

  • Using the java.class.path system property directly assumes certain runtime behavior, rather than inspecting the result of actual behavior. This assumption holds in many cases, but fails in others.

The ideal solution would be a way to enumerate resources loaded by the system (and possibly platform) classloader that works on Java 9+, so that the entire classpath hierarchy is enumerable.

Absent this, relying on the java.class.path property is likely necessary for Java 9+. If doing so, a next-best solution should accommodate at least two particular cases:

  1. The application may be running in a container, and
  2. Dynamic classloaders may add resources at runtime.
I found this, which claims to be a general-purpose classpath enumerator. Haven't tried it yet https://github.com/classgraph/classgraph
I'd seen that, but also haven't had time to dig into it. A large bit of the searchable information available seems to be traceable to that library's author. His notes for Jigsaw support are somewhat helpful: https://github.com/classgraph/classgraph/issues/36. His eureka moment doesn't seem to get us there, though.

I took a quick stab at using the module system, but it wasn't smooth sailing. Some notes:

- In the Java 9+ module system, all classes on the classpath belong to the unnamed module, of which each classloader has its own.
- A module's resources are enumerable from a ModuleReference object.
- Unfortunately, unnamed modules are not real modules; they are not in a module layer or configuration.
- As a result, there does not seem to be a way to get a ModuleReference from the unnamed module's Module object.