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.
instanceof behaviour
A technical definition of instanceof
, from the Java Language Specification:
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 java.net.URLClassLoader @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.
Be First to Comment