I recently encountered an intriguing issue with casting objects in Java when dealing with objects loaded by different classloaders. In a modular application that dynamically loads plugins at runtime, each plugin is loaded using a separate classloader to maintain isolation and avoid conflicts.

Two classes were involved: PluginClass and MainAppClass. In MainAppClass, I needed to cast an object to PluginClass. I am not an expert in java or dynamically class loading. So, I was surprisined that the cast operation failed even though the object was an instance of PluginClass.

The issue stemmed from the objects being loaded by different classloaders. In Java, casting not only checks the class of the object but also the classloader that loaded the class. Since PluginClass was loaded by a plugin-specific classloader and MainAppClass was loaded by the main application classloader, the cast operation failed despite the object’s class matching PluginClass.

To address this, I needed to reconsider the classloading strategy. One approach was to establish a parent-child relationship between the classloaders to allow the cast to work correctly. Alternatively, using reflection to load the class and perform the cast explicitly proved to be effective.

Here’s the reflection-based solution for casting:

public Object castToClass(Object obj, String className) {
    try {
        Class<?> clazz = Class.forName(className, true, obj.getClass().getClassLoader());
        return clazz.cast(obj);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return null;
    }
}

This method dynamically loads the class using the object’s classloader and then casts the object to that class, ensuring the correct behavior regardless of classloader differences.

In our case, we were loading JDBC driver jars, and need to cast the connection objects instead, so I instead unwrapped the JDBC connection, instead of casting it, but the core principle is the same.

public Object unwrapToClass(Connection conn, String className) {
        try {
      Class<?> clazz = classLoader.loadClass(className);
      if (conn.isWrapperFor(clazz)) {
        Object unwrappedConn = conn.unwrap(clazz);
        return unwrappedConn
      }
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
}