Skip to content

Classes in multiple ClassLoaders

When an application loads classes using multiple ClassLoaders, unexpected behaviours can arise. For example, consider a class loaded by two different classloaders:

Object instance1 = new MyCustomClass("42");
Object instance2 = applicationClassLoader.loadClass("MyCustomClass").getDeclaredConstructor(String.class).newInstance("42");
Object instance3 = customClassLoader.loadClass("MyCustomClass").getDeclaredConstructor(String.class).newInstance("42");

System.out.println("instance1 instanceof MyCustomClass: " + (instance1 instanceof MyCustomClass));
System.out.println("instance2 instanceof MyCustomClass: " + (instance2 instanceof MyCustomClass));
System.out.println("instance3 instanceof MyCustomClass: " + (instance3 instanceof MyCustomClass));

Result is:

instance1 instanceof MyCustomClass: true
instance2 instanceof MyCustomClass: true
instance3 instanceof MyCustomClass: false

All three Objects are instances of the same MyCustomClass and yet we can make instanceof evaluate as false in some circumstances.

Instantiation behaviour

In the example above, we create three instances using two different classloaders. instance1 creates an instance with the new keyword. This is a shorthand for requesting that the current classloder (the one that loaded the running class) creates a new instance. So instance1 is semantically equivalent to instance2:

ClassLoader applicationClassLoader = Main.class.getClassLoader();
Object instance1 = new MyCustomClass("42");
Object instance2 = applicationClassLoader.loadClass("MyCustomClass").getDeclaredConstructor(String.class).newInstance("42");

However, we can explicitly load the class with any classloader and use the newInstance method to instantiate it. For instance3, I created a URLClassLoader and loaded my class from the .class file on disk:

URL classLocation = new File("classes/").toURI().toURL();
try (URLClassLoader customClassLoader = new URLClassLoader(new URL[]{classLocation}, n
ull)) {
    Object instance3 = customClassLoader.loadClass("MyCustomClass").getDeclaredConstructor(String.class).newInstance("42");

To be clear, instance1 and instance2 are instantiated in a semantically equivalent way. We’ve obtained the MyCustomClass loaded by the applicationClassLoader and created a new instance of it.

instance3 is different. Here we obtain MyCustomClass loaded by customClassLoader and instantiate that. Even though both MyCustomClasses are loaded from the same bytes on disk, they’re different instances. This is why instance3 instanceof MyCustomClass evaluates as false. It is not an instance of the MyCustomClass that belongs to the application class loader.

Classloader diagram: Application ClassLoader and URLClassLoader each have their own MyCustomClass

instanceof behaviour

A technical definition of instanceof, from the Java Language Specification:

At run time, the result of the instanceof operator is true if the value of the RelationalExpression is not null and the reference could be cast (ยง15.16) to the ReferenceType without raising a ClassCastException. Otherwise the result is false.

Java Language Specification, Java SE7 chapter 15.20.2

The behaviours above are consistent with the specification. If we attempt to cast instance3 to MyCustomClass (the one belonging to the application’s classloader), we get a ClassCastException.

java.lang.ClassCastException: class MyCustomClass cannot be cast to class MyCustomClass (MyCustomClass is in unnamed module of loader @2ac1fdc4; MyCustomClass is in unnamed module of loader 'app')

The start of the message is a little odd (class MyCustomClass cannot be cast to class MyCustomClass) but helpfully the remainder of the message explains the problem exactly.

But I don’t use classloaders…

You might think that this little edge case of the Java language does not affect you as you never create your own classloaders or instantiate objects in this way. But keep in mind that your framework or application server might be doing this for you behind the scenes. For example the JBoss / Wildfly Application Server uses multiple class loaders to load different resources. I’ve also seen this strange behaviour in Avaya Orchestration Designer reusable modules but then again strange behaviour is normal for Avaya!

Any application framework that allows you to dynamically load or reload ears, wars, jars or individual classes is probably doing something clever with classloaders and you should at least have a basic understanding of how this works so that you don’t fall foul of these subtleties.

Full example code in Git Gist.

Published inJava Basics

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *