How to load JAR files dynamically at Runtime?

341,677

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.

Share:
341,677
Allain Lalonde
Author by

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, 2022

Comments

  • Allain Lalonde
    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
    Allain Lalonde over 15 years
    Only 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
    darrickc almost 15 years
    This method works great when running in my IDE, but when I build my JAR I get a ClassNotFoundException when calling Class.forName().
  • Ranger
    Ranger almost 13 years
    Using 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
    Huckle almost 12 years
    I 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
    angelcervera almost 12 years
    ServiceLoader don't add jar files dynamically at runtime. jar files must be in classpath previously.
  • Sergey Karpushin
    Sergey Karpushin over 9 years
    It'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
    jaw over 9 years
    Works. 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 called my.jar and is located in the same directory.
  • Andrei Savu
    Andrei Savu about 9 years
    All 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
    Jason S over 8 years
    Um. 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
    Zero3 over 8 years
    Your 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
    Gus almost 8 years
    Doesn't work so well if the system class loader happens to be something other than a URLClassLoader...
  • Gobliins
    Gobliins over 7 years
    Does this also work when replacing a jar file (which is in the classpath already) ?
  • Allain Lalonde
    Allain Lalonde over 7 years
    I'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
    ManoDestra over 7 years
    What 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
    johnstosh about 7 years
    Don't forget to URL url = file.toURI().toURL();
  • johnstosh
    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
    Charlweed over 5 years
    Java 9+ warns that URLClassLoader.class.getDeclaredMethod("addURL", URL.class) is an illegal use of reflection, and will fail in the future.
  • Eray Erdin
    Eray Erdin over 5 years
    I'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
    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
    Richard Żak about 5 years
    With Java 11.0.2, I get: Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClas‌​sPathForInstrumentat‌​ion(java.lang.String‌​) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
  • FiReTiTi
    FiReTiTi about 5 years
    Any idea how to update this code to work with Java 9+?
  • user7294900
    user7294900 almost 5 years
    Can I also remove jar?
  • Sergei Ledvanov
    Sergei Ledvanov over 4 years
    For 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
    Jan over 4 years
    Works with Java 8 EE in application server environment.
  • Mordechai
    Mordechai over 4 years
    @FiReTiTi Yes!!
  • Vishal Biyani
    Vishal Biyani almost 4 years
    Thank you - this is really helpful! All other references on web use methods for JDK 8 or before - which has multiple problems.
  • GhostCat
    GhostCat over 3 years
    Whilst 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
    Brain over 3 years
    Maybe 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
    Mike Preradovic over 3 years
    Add to VM options: -add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED
  • iganev
    iganev over 3 years
    This 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
    Davide about 3 years
    I'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
    Evgeniy Egorov almost 3 years
    A problem is java -cp <your jar> <MainClass>. Where is no instrumentation.
  • DarthGizka
    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
    Nick Legend almost 3 years
    This doesn't work for me with Java 11, saying java.lang.ClassNotFoundException: package01.WorkerImpl.
  • Nick Legend
    Nick Legend almost 3 years
    addURL() will not work in Java 9 as ClassLoader.getSystemClassLoader() is not URLClassLoader since Java 9.
  • searchengine27
    searchengine27 almost 3 years
    lol 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
    Allain Lalonde over 2 years
    lol. I'm certainly not going back and editing and answer I gave almost 14 years ago.
  • Valsaraj Viswanathan
    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
    user2209562 over 2 years
    After 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
    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
    user2209562 over 2 years
    I 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
    user2209562 over 2 years
    btw 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
    Mordechai over 2 years
    Ah, no. Just modify your class loader to expose the protected addUrl and you don't need reflection. It's actually my last sentence
  • user2209562
    user2209562 over 2 years
    Maybe I am missing something or is it specific to Spring Boot. Well I created a separate question for it "stackoverflow.com/questions/70066951/…"
  • Holger
    Holger over 2 years
    As 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
    Breina over 2 years
    @ErayErdin Seems like it does now.
  • skmdvcoep2000
    skmdvcoep2000 over 2 years
    With 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.invo‌​ke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invo‌​ke(NativeMethodAcces‌​sorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.‌​invoke(DelegatingMet‌​hodAccessorImpl.java‌​:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566)
  • ajayg2808
    ajayg2808 over 2 years
    Not working with java 11
  • RamMohan222
    RamMohan222 over 2 years
    If 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.