Search code examples
javaclassloaderdynamic-class-loaders

Unexpected behaviour in JVM class loading (ClassNotFoundException before the class is really needed)


I would need help trying to understand why this is happening to me:

Using Java 1.8.0_131, I have a class such as this:

public class DynamicClassLoadingAppKO {

    /*
     * THIS VERSION DOES NOT WORK, A ClassNotFoundException IS THROWN BEFORE EVEN EXECUTING main()
     */


    // If this method received ChildClassFromLibTwo, everything would work OK!
    private static void showMessage(final ParentClassFromLibOne obj) {
        System.out.println(obj.message());
    }


    public static void main(final String[] args) throws Throwable {

        try {

            final ChildClassFromLibTwo obj = new ChildClassFromLibTwo();
            showMessage(obj);

        } catch (final Throwable ignored) {
            // ignored, we just wanted to use it if it was present
        }

        System.out.println("This should be displayed, but no :(");

    }

}

Two other classes are being used up there: ParentClassFromLibOne and ChildClassFromLibTwo. The latter extends from the former.

There are two external libraries involved:

  • One library is called libone and contains the ParentClassFromLibOne class. The application includes this library in the classpath both for compiling and executing.
  • A second library is called libtwo and contains the ChildClassFromLibTwo class. The application includes this library in the classpath for compiling, but not for executing.

As far as I understand, the Java runtime should try to load the ChildClassFromLibTwo (which is not in the classpath at runtime) at this line:

final ChildClassFromLibTwo obj = new ChildClassFromLibTwo();

Given this class is not in the classpath, a ClassNotFoundException should be thrown, and given this line is inside a try...catch (Throwable), the System.out.println line at the end should be executed anyway.

However, what I get is the ClassNotFoundException thrown when the DynamicClassLoadingAppKO itself is loaded, apparently before the main() method is executed at all, and therefore not caught by the try...catch.

What seems more strange to me is that this behaviour disappears and everything works as I would expect if I change the signature of the showMessage() method so that instead of receiving an argument of the parent class, it is directly of the child class:

/*
 * THIS VERSION WORKS OK, BECAUSE showMessage RECEIVES THE CHILD CLASS AS A PARAMETER
 */
private static void showMessage(final ChildClassFromLibTwo obj) {
    System.out.println(obj.message());
}

How is this possible? What am I missing in the way class loading works?

For testing convenience, I have created a GitHub repository replicating this behaviour [1].

[1] https://github.com/danielfernandez/test-dynamic-class-loading/tree/20170504


Solution

  • OK, the details of why this happens are explained in this Spring Boot ticket [1] which I've been very lucky to be promptly pointed to by Andy Wilkinson. That was definitely a difficult one IMO.

    Apparently, what happens in this case is that when the calling class itself is loaded, the verifier kicks in and sees that the showMessage() method receives an argument of type ParentClassFromLibOne. So far so good, and this would not provoke a ClassNotFoundException at this phase even if ParentClassFromLibOne was not in the classpath at runtime.

    BUT apparently the verifier also scans method code and notes that there is a call in main() to that showMessage() method. A call that does not pass as an argument a ParentClassFromLibOne, but instead an object of a different class: ChildClassFromLibTwo.

    So it is in this case that the verifier does try to load ChildClassFromLibTwo in order to be able to check if it really extends from ParentClassFromLibOne.

    Interestingly this wouldn't happen if ParentClassFromLibOne was an interface, because interfaces are treated as Object for assignment.

    Also, this does not happen if showMessage(...) directly asks for a ChildClassFromLibTwo as an argument because in such case the verifier does not need to load the child class to check it is compatible... with itself.

    Daniel, I'm voting up your answer but I will not mark it as accepted because I consider it fails at explaining the real reason why this is happening at verify time (it's not the class in the signature of the method that's causing the ClassNotFoundException).

    [1] https://github.com/spring-projects/spring-boot/issues/8181