Custom URLClassLoader, NoClassDefFoundError when run

Jonah picture Jonah · Apr 27, 2011 · Viewed 8.1k times · Source

I've created my own URLClassLoader, and set it as the system classloader via java.system.class.loader. It's initialized and everything, but the classes I'm trying to load aren't found. Here's the URLClassLoader:

public class LibraryLoader extends URLClassLoader
{
    public LibraryLoader(ClassLoader classLoader)
    {
        super(new URL[0], classLoader);
    }
    synchronized public void addJarToClasspath(String jarName) throws MalformedURLException, ClassNotFoundException
    {
        File filePath = new File(jarName);
        URI uriPath = filePath.toURI();
        URL urlPath = uriPath.toURL();

        System.out.println(filePath.exists());
        System.out.println(urlPath.getFile());

        addURL(urlPath);
    }
}

I've confirmed that the jar exists, and that the path is correct. This is how I call it in my program:

LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
loader.addJarToClasspath("swt.jar");

This is the exception that I get (line 166 refers to the line at which I try to create a new Point:

Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/graphics/Point
        at mp.MyProgram.loadArchitectureLibraries(MyProgram.java:116)
        at mp.MyProgram.main(MyProgram.java:90)
Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.graphics.Point
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 2 more

I even tried explicitly loading the class like so:

Class.forName("org.eclipse.swt.graphics.Point", false, loader);

What might be causing this? Shouldn't it "just work"?


Update: Here's the important code from MyProgram

public class MyProgram
{
    // ...

    public static void main(String[] args)
    {
        loadArchitectureLibraries();

        // ...
    }

    public static void loadArchitectureLibraries()
    {
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
    }
}

Update 2: Here's an SSCCE: http://nucleussystems.com/files/myprogram.zip . Call java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar.

Answer

Kris Babic picture Kris Babic · Apr 27, 2011

I would have to agree with the comments on this question. Based on the code you have provided, it would appear that you are getting the error due to the JAR files not being where you expect them to be. As mentioned by @Andrew, you are not checking the existence of the file in your addJarToClasspath method. As a result, if the file does not exist, you will receive a ClassNotFound exception as you are seeing. I verified this problem by taking your ClassLoader logic and passing it a valid and an invalid JAR. When a valid JAR/path was provided, the ClassLoader loaded the class as expected. When an invalid JAR/path was specified, I received the error you mentioned. The URLClassLoader does not throw an exception if an URL is specified that does not point to a valid file.

To validate the scenario, print out the path of the full path of your File and see if it is correct for the execution environment.

Edit


It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:

  • Use a script to detect the environment and set the classpath accordingly. This is perhaps the simplest solution, but one you may or may not want to take based on your particular requirements.
  • Similar to what was mentioned in other answers, specifically load and execute your application using your custom ClassLoader. This does not mean creating a single class that will be loaded and then invoke your application. It means that any class that needs to interact with the dynamically loaded swt libraries and any classes that need to reference your application classes should be loaded from your custom ClassLoader. Any application dependencies, such as log4j, etc, can be referenced by the default application ClassLoader. Here is an example of how this would work:

JAR 1 (launcher.jar):

public class AppLauncher {
    public static void main(String… args) throws Exception {
        ClassLoader loader = initClassLoader();
        Class<?> mpClass = loader.loadClass("mp.MyProgram");

        // using Runnable as an example of how it can be done
        Runnable mpClass = (Runnable) mpClass.newInstance();
    }
    public static ClassLoader initClassLoader() {
        // assuming still configured as system classloader, could also be initialized directly
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        // add the main application jar to the classpath.  
        // You may want to dynamically determine this value (lib folder) or pass it in as a parameter
        loader.addJarToClasspath("myapp.jar");

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
        return loader;
    }

JAR 2 (myapp.jar): Includes all class which depend on swt

public class MyProgram implements Runnable {
    //…
    public void run() {
    // perform application execution

           // this logic should now work
           org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0,0);
    }
}

The AppLauncher class would be executed by the VM without the rest of your application being included in the execution Jar.

java -Djava.system.class.loader=test.LibraryLoader -cp <dependency jars>:launcher.jar mp.AppLauncher

I see that there have been updates to the other answers. Since I already had typed up the above comments, I figured that I should still post it for your perusal.