Search code examples
javaclassloaderurlclassloader

Why this URLClassLoader sometimes works and sometimes doesn't?


I have a quite complex Java software that must load dynamically a class, create an object and invoke one of its methods. It turns out that in some situations this raises a NoClassDefFoundError (caused by a ClassNotFoundException) if the loaded class refers another one.

Say we have:

public interface Go {
    void go();
}

Say that the class to be loaded is:

package foo;
import static baz.Baz.BAZ;
public class Foo implements Go {
    @Override
    void go() { 
        ...
        something = BAZ;
        ...
    }
}

where

package baz;
public class Baz {
    public static final int BAZ = 111;
    ...
}

Say that the class is loaded this way:

try (final URLClassLoader loader = new URLClassLoader(new URL[] { url })) {
    final Class<? extends Foo> clazz =  
        loader.loadClass("foo.Foo").asSubclass(Go.class);
    this.obj = clazz.newInstance();
    this.obj.go();
} catch ...

Then everything works. Say that instead you do things this other way:

try (final URLClassLoader loader = new URLClassLoader(new URL[] { url })) {
    final Class<? extends Foo> clazz =  
        loader.loadClass("foo.Foo").asSubclass(Go.class);
    this.obj = clazz.newInstance();
    //we do not invoke go() now...
} catch ...

//later, in another method...
this.obj.go();

Now the invocation of go() raises a NoClassDefFoundError, complaining it cannot load baz.Baz. Of course the url is the same. Of course both foo.Foo and baz.Baz are in the path indicated by url. The only difference I can see is the moment when the go() method is invoked.

What is wrong here?


Solution

  • What is wrong here?

    I think that the problem is this:

    try (final URLClassLoader loader = new URLClassLoader(new URL[] { url })) {
    

    That will cause the classloader to be closed when you exit the try block. The close() method's javadoc says:

    Closes this URLClassLoader, so that it can no longer be used to load new classes or resources that are defined by this loader.

    Calling the go() method is triggering class initialization for the Go class, and that is what is causing the Baz class to be loaded and initialized. If the latter class loading occurs after the classloader has been closed, it is going to fail.