How to load JAR files dynamically at Runtime?
Solution 1
The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:
URLClassLoader child = new URLClassLoader(
new URL[] {myJar.toURI().toURL()},
this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);
Painful, but there it is.
Solution 2
The following solution is hackish, as it uses reflection to bypass encapsulation, but it works flawlessly:
File file = ...
URL url = file.toURI().toURL();
URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);
Solution 3
You should take a look at OSGi, e.g. implemented in the Eclipse Platform. It does exactly that. You can install, uninstall, start and stop so called bundles, which are effectively JAR files. But it does a little more, as it offers e.g. services that can be dynamically discovered in JAR files at runtime.
Or see the specification for the Java Module System.
Solution 4
How about the JCL class loader framework? I have to admit, I haven't used it, but it looks promising.
Usage example:
JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)
JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class
Object obj = factory.create(jcl, "mypackage.MyClass");
Solution 5
While most solutions listed here are either hacks (pre JDK 9) hard to configure (agents) or just don't work anymore (post JDK 9) I find it really surprising that nobody mentioned a clearly documented method.
You can create a custom system class loader and then you're free to do whatever you wish. No reflection required and all classes share the same classloader.
When starting the JVM add this flag:
java -Djava.system.class.loader=com.example.MyCustomClassLoader
The classloader must have a constructor accepting a classloader, which must be set as its parent. The constructor will be called on JVM startup and the real system classloader will be passed, the main class will be loaded by the custom loader.
To add jars just call ClassLoader.getSystemClassLoader()
and cast it to your class.
Check out this implementation for a carefully crafted classloader. Please note, you can change the add()
method to public.
Allain Lalonde
I'm a Software Developer working on Alternative User Interfaces for business projects. I love working on things that make you "Why?".
Updated on July 08, 2022Comments
-
Allain Lalonde almost 2 years
Why is it so hard to do this in Java? If you want to have any kind of module system you need to be able to load JAR files dynamically. I'm told there's a way of doing it by writing your own
ClassLoader
, but that's a lot of work for something that should (in my mind at least) be as easy as calling a method with a JAR file as its argument.Any suggestions for simple code that does this?
-
Allain Lalonde over 15 yearsOnly problem with this approach is that you need to know what classes are in what jars. As opposed to just loading a directory of jars and then instantiating classes. I am misunderstanding it?
-
darrickc almost 15 yearsThis method works great when running in my IDE, but when I build my JAR I get a ClassNotFoundException when calling Class.forName().
-
Ranger almost 13 yearsUsing this approach you need to make sure you won't call this load method more than once for each class. Since you're creating a new class loader for every load operation, it can not know whether the class was already loaded previously. This can have bad consequences. For example singletons not working because the class was loaded several times and so the static fields exist several times.
-
Huckle almost 12 yearsI hate to bump an old thread, but I would like to point out that all content on stackoverflow is CC licensed. Your copyright statement is effectively ineffective. stackoverflow.com/faq#editing
-
angelcervera almost 12 yearsServiceLoader don't add jar files dynamically at runtime. jar files must be in classpath previously.
-
Sergey Karpushin over 9 yearsIt's also buggy and missing some important implementations i.e. findResources(...). Be ready to spend wonderful nights investigating why certain things don't work =)
-
jaw over 9 yearsWorks. Even with dependencies to other classes inside the jar. The first line was incomplete. I used
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
assuming that the jar file is calledmy.jar
and is located in the same directory. -
Andrei Savu about 9 yearsAll the activity on this response makes me wonder how much hacks we are running in production in different systems. I'm not sure I want to know the answer
-
Jason S over 8 yearsUm. Technically, original content is CC licensed, but if you post copyrighted content here, it doesn't remove the fact that the content is copyrighted. If I post a picture of Mickey Mouse, it doesn't make it CC-licensed. So I'm adding the copyright statement back.
-
Zero3 over 8 yearsYour answer seems a little confusing, and is perhaps more suited as a comment to the answer by jodonnell if it is merely a simple improvement.
-
Gus almost 8 yearsDoesn't work so well if the system class loader happens to be something other than a URLClassLoader...
-
Gobliins over 7 yearsDoes this also work when replacing a jar file (which is in the classpath already) ?
-
Allain Lalonde over 7 yearsI'm afraid I don't know how it behaves when replacing jars. I imagine there's some caching going on keeping it from hitting the disk every time. Try it and let us know.
-
ManoDestra over 7 yearsWhat is the class type of the myJar variable? Would be useful to have the instantiation of that variable added to the example so that the sample is entirely standalone.
-
johnstosh about 7 yearsDon't forget to URL url = file.toURI().toURL();
-
johnstosh about 7 years@Allain and other people, you can load a directory of jars like this: <code>ClassLoader loader = URLClassLoader.newInstance( new URL[] { yourURL }, getClass().getClassLoader() );</code>
-
Charlweed over 5 yearsJava 9+ warns that
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
is an illegal use of reflection, and will fail in the future. -
Eray Erdin over 5 yearsI'm still wondering @SergeyKarpushin's claims are still present since the project has been updated over time to second major version. Would like to hear experience.
-
Sergey Karpushin over 5 years@ErdinEray, it is a very good question that I ask myself too since we were "forced" to switch to OpenJDK. I still work on java projects, and I don't have any evidences that Open JDK will fail you these days (I had issue back then though). I guess I withdraw my claim until I bump into something else.
-
Richard Żak about 5 yearsWith Java 11.0.2, I get:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
-
FiReTiTi about 5 yearsAny idea how to update this code to work with Java 9+?
-
user7294900 almost 5 yearsCan I also remove jar?
-
Sergei Ledvanov over 4 yearsFor some reason when using this solution I get "Exception in thread "main" java.lang.ClassNotFoundException: agent.Agent". I packaged "Agent" class into my main "war" application, so I am sure it's there
-
Jan over 4 yearsWorks with Java 8 EE in application server environment.
-
Mordechai over 4 years@FiReTiTi Yes!!
-
Vishal Biyani almost 4 yearsThank you - this is really helpful! All other references on web use methods for JDK 8 or before - which has multiple problems.
-
GhostCat over 3 yearsWhilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference. See here for instructions how to write better "link-based" answers.
-
Brain over 3 yearsMaybe it's a security problem. However, if a hacker already has access to the system, that one leak is the least of the problem.
-
Mike Preradovic over 3 yearsAdd to VM options: -add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED
-
iganev over 3 yearsThis is the only solution that actually worked for me. I am using the externally loaded JAR in an ExecutorService tasks and it was mysteriously hanging with all other approaches..
-
Davide about 3 yearsI've two jars (jar1 and jar2). I wish to load the class Abc that resides in the default package of jar1 and jar2. With this approach the class Abc from the jar1 is always loaded, so in my case this snippet code is not working
-
Evgeniy Egorov almost 3 yearsA problem is java -cp <your jar> <MainClass>. Where is no instrumentation.
-
DarthGizka almost 3 years@czdepski: thanks for finding a solution that can be easily and cleanly integrated in a post-SE11 project. You saved my bacon! ;-)
-
Nick Legend almost 3 yearsThis doesn't work for me with Java 11, saying
java.lang.ClassNotFoundException: package01.WorkerImpl
. -
Nick Legend almost 3 years
addURL()
will not work in Java 9 asClassLoader.getSystemClassLoader()
is notURLClassLoader
since Java 9. -
searchengine27 almost 3 yearslol the use of the phrase, 'works flawlessly' in this answer is quite amusing...considering not only is it abusive to the API and violates security, but as of Java9 the systemClassLoader is no longer a
URLClassloader
, as it was never promised it would be via the API. -
Allain Lalonde over 2 yearslol. I'm certainly not going back and editing and answer I gave almost 14 years ago.
-
Valsaraj Viswanathan over 2 years-Djava.system.class.loader=com.example.MyCustomClassLoader seems not reflected for the compile time dependency jars in the application jar manifest. Please see: stackoverflow.com/questions/68380968/…
-
user2209562 over 2 yearsAfter startup with the Custom Classloader, can the jar files be loaded exactly the same as in Allain answer? Still get "jdk.internal.loader.ClassLoaders$AppClassLoader is in module java.base of loader 'bootstrap'; org.myapp.CustomClassLoader is in unnamed module of loader 'app'"
-
Mordechai over 2 years@user2209562 I'm not sure what you might be doing wrong, but you should be able to access your classloader just like with Allain's answer
-
user2209562 over 2 yearsI tried it like this:
DynamicClassLoader classLoader = (DynamicClassLoader)ClassLoader.getSystemClassLoader(); Method method = DynamicClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(classLoader, url);
-
user2209562 over 2 yearsbtw I also checked the update4j code and there they do it like this
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); DynamicClassLoader dynamic = DynamicClassLoader.findAncestor(contextClassLoader); dynamic.add(url);
But this seems like another use case. -
Mordechai over 2 yearsAh, no. Just modify your class loader to expose the protected addUrl and you don't need reflection. It's actually my last sentence
-
user2209562 over 2 yearsMaybe I am missing something or is it specific to Spring Boot. Well I created a separate question for it "stackoverflow.com/questions/70066951/…"
-
Holger over 2 yearsAs mentioned in the answer, the original system class loader becomes the parent loader of the custom loader, which means, classes loaded through the original class loader don’t see classes defined by the custom loader, which is important, as the class path is still handled through the original loader, i.e.
getURLs()
of the custom loader does not return the class path, and when the custom loader follows the standard delegation model, the request for the main class will be forwarded to the old standard loader which will load it the same way as without the custom loader. -
Breina over 2 years@ErayErdin Seems like it does now.
-
skmdvcoep2000 over 2 yearsWith openjdk 11 this approach fails with exception java.lang.IllegalArgumentException: object is not an instance of declaring class at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566)
-
ajayg2808 over 2 yearsNot working with java 11
-
RamMohan222 over 2 yearsIf we use Java SPI probably we even don't need to know about the class name as well, SPI can scan and load the implemented classes.