Search code examples
javaexceptiondynamicclassloader

Dynamic classloading fails on runtime


I have the following snippet of java code:

final Class<?> junitCoreClass = AccessController.doPrivileged(
    new PrivilegedAction<URLClassLoader>() {
      @Override
      public URLClassLoader run() {
        return new URLClassLoader(new URL[] { junitJarUrl });
      }
    }).loadClass("org.junit.runner.JUnitCore");

System.out.println(junitCoreClass.getName());
final JUnitCore junitCore = (JUnitCore) junitCoreClass.newInstance();

This compiles fine. But when I try to run it, something weird happens; a java.lang.NoClassDefFoundError is thrown on that last line, referring to the class just loaded. The weird part is, the println prints the exact class name.

I checked that if I keep the new instance reference as an Object and manipulate it only through reflection, everything's fine, so the offending piece of code must be the explicit cast.

Can someone explain to me why this happens, and also tell me how I can achieve what I'm trying to do?

PS: For those who wants to see a closer stack trace, there's not much to show:

java.lang.NoClassDefFoundError: org/junit/runner/JUnitCore
  at [last line of example)
  [lines from my app]
Caused by: java.lang.ClassNotFoundException: org.junit.runner.JUnitCore
  at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
  at java.security.AccessController.doPrivileged(Native Method)
  at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:315)
  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:330)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:250)
  at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:398)
  at [last line of example]
  [lines from my app]

Solution

  • The problem is that your main class is loaded by the system class loader (the one containing -classpath) that does not have JUnit on the classpath. Then, you create a separate class loader that has only JUnit on the class path. When your main class attempts to cast to JUnitCore, the system class loader is asked to load JUnitCore, which it does not contain, so NoClassDefFoundError occurs.

    There is no convenient way to do what you're trying to do without using reflection. You will need to (1) create a separate class that accesses JUnitCore directly, (2) include a path to that class on URLClassLoader (directory or JAR), (3) use reflection to load that class, and (4) use reflection to invoke a method on that class.