74

Is it possible to add a file (not necessarily a jar file) to java classpath at runtime. Specifically, the file already is present in the classpath, what I want is whether I can add a modified copy of this file to the classpath.

Thanks,

hlt
  • 6,219
  • 3
  • 23
  • 43

7 Answers7

47

You can only add folders or jar files to a class loader. So if you have a single class file, you need to put it into the appropriate folder structure first.

Here is a rather ugly hack that adds to the SystemClassLoader at runtime:

import java.io.IOException;
import java.io.File;
import java.net.URLClassLoader;
import java.net.URL;
import java.lang.reflect.Method;

public class ClassPathHacker {

  private static final Class[] parameters = new Class[]{URL.class};

  public static void addFile(String s) throws IOException {
    File f = new File(s);
    addFile(f);
  }//end method

  public static void addFile(File f) throws IOException {
    addURL(f.toURL());
  }//end method


  public static void addURL(URL u) throws IOException {

    URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Class sysclass = URLClassLoader.class;

    try {
      Method method = sysclass.getDeclaredMethod("addURL", parameters);
      method.setAccessible(true);
      method.invoke(sysloader, new Object[]{u});
    } catch (Throwable t) {
      t.printStackTrace();
      throw new IOException("Error, could not add URL to system classloader");
    }//end try catch

   }//end method

}//end class

The reflection is necessary to access the protected method addURL. This could fail if there is a SecurityManager.

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
Thilo
  • 257,207
  • 101
  • 511
  • 656
  • Thanks for the reply guys, I'm guessing we can also add a plain file (not a class or jar etc...) in the classpath, But how do I know which one would get picked up (the latest addition the the classpath or the old file) –  Jun 18 '09 at 08:11
  • 1
    Ah, that is a problem. Much of this is implementation dependent, so you should really not have to classes (or other resources) with the same name in the same classloader. – Thilo Jun 18 '09 at 08:24
  • If you put them into separate classloaders, then there is a spec. Usually the parent classloader takes precendence, for webapps it is sometimes the other way around (but still well-defined). The bootloader always comes first. – Thilo Jun 18 '09 at 08:39
  • Is it really implementation-dependent? The Javadoc for `URLClassLoader` refers to the list of URLs as a "search path", says they'll be "searched in the order specified", and for `addURL` it specifically says it "appends" the new entry. That sounds well-defined too. – Andrew Janke Mar 20 '14 at 05:31
  • 1
    This throws java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader – Arquillian Apr 28 '20 at 13:58
  • 3
    Note for anyone who runs across this answer in the future. This code assumes that the loader returned by `getSystemClassLoader()` is an instance of `URLClassLoader`, which is no longer true as of Java 9. – Stuart Marks Jun 14 '21 at 17:42
44

Try this one on for size.

private static void addSoftwareLibrary(File file) throws Exception {
    Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
    method.setAccessible(true);
    method.invoke(ClassLoader.getSystemClassLoader(), new Object[]{file.toURI().toURL()});
}

This edits the system class loader to include the given library jar. It is pretty ugly, but it works.

Chase
  • 1,419
  • 12
  • 17
  • Great answer. I would however edit it a little, making use of varargs to keep it even more simpler. – James Selvakumar Jan 05 '17 at 01:34
  • Works! I reduced it by removing the arrays, as the parameters are varargs (by now?) so no arrays needed anymore. – BAERUS Sep 17 '19 at 09:06
  • 2
    Note for anyone who runs across this answer in the future. This code assumes that the loader returned by `getSystemClassLoader()` is an instance of `URLClassLoader`, which is no longer true as of Java 9. – Stuart Marks Jun 14 '21 at 17:42
14

The way I have done this is by using my own class loader

URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
DynamicURLClassLoader dynalLoader = new DynamicURLClassLoader(urlClassLoader);

And create the following class:

public class DynamicURLClassLoader extends URLClassLoader {

    public DynamicURLClassLoader(URLClassLoader classLoader) {
        super(classLoader.getURLs());
    }

    @Override
    public void addURL(URL url) {
        super.addURL(url);
    }
}

Works without any reflection

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
Ranjit Aneesh
  • 147
  • 1
  • 5
  • This solution sounded good to me, but it does not work (maybe because I'm in a Tomcat webapp?). I get a "class not found" error when trying to call a method from a class contained in the dynamic loaded JAR. The other solutions do work, even in a webapp. – toni07 Mar 19 '15 at 09:58
  • This doesn't work since you create a new classloader, the other classloaders still won't access the file you added to this classloader – cinqS May 09 '17 at 09:00
  • 2
    Note for anyone who runs across this answer in the future. This code assumes that the loader returned by `getSystemClassLoader()` is an instance of `URLClassLoader`, which is no longer true as of Java 9. – Stuart Marks Jun 14 '21 at 17:43
6

You coud try java.net.URLClassloader with the url of the folder/jar where your updated class resides and use it instead of the default classloader when creating a new thread.

Tobias Schulte
  • 3,765
  • 1
  • 29
  • 33
0

Yes I believe it's possible but you might have to implement your own classloader. I have never done it but that is the path I would probably look at.

ssteidl
  • 297
  • 1
  • 5
-1

yes, you can. it will need to be in its package structure in a separate directory from the rest of your compiled code if you want to isolate it. you will then just put its base dir in the front of the classpath on the command line.

akf
  • 38,619
  • 8
  • 86
  • 96
-2

My solution:

File jarToAdd = new File("/path/to/file");

new URLClassLoader(((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs()) {
    @Override
    public void addURL(URL url) {
         super.addURL(url);
    }
}.addURL(jarToAdd.toURI().toURL());
  • 1
    Please explain what your code does (__in the answer itself, not the comments__) in addition to providing it. – Nic Apr 30 '15 at 14:45
  • This doesn't work because the `addURL` method in `URLClassLoader` is `protected`. But you can write your own URLClassLoader which rewrites the method and the modifier should be accessible. – cinqS May 09 '17 at 08:47
  • 1
    This adds the file to the temporary loader, not the system loader, without any persistent effect. Starting with Java 9, it will fail with an exception, due to the wrong assumption that `getSystemClassLoader()` returns an `URLClassLoader`. – Holger Jun 10 '22 at 09:29