0

What are the possible reasons why Java will throws Cannot Cast Exception when loading class dynamically?

Example:

    String classToLoad = null;
    try {
        classToLoad = extractMainClassManifest(jar);
        LOGGER.info("Class to load: " + classToLoad);
        JarByteClassloader loader = new JarByteClassloader(jar); 
        // e.g. class MyJarEntryObject extends JarEntryObject
        Class c = loader.loadClass(classToLoad); 
        JarEntryObject jarEntry = (JarEntryObject) c.newInstance();
        // other stuff
    } catch (Exception e) {
        e.printStackTrace();
    }

Where this code throws that error, I don't suppose that there is another Class loader here (the case of multiple class loaders).

This code works fine when running with JUnit or just running in a main() method. The cast problem appears when running on a Servlet container (e.g. mvn jetty:run)

Log:

java.lang.ClassCastException: com.mywebapp.example.MyJarEntryObject cannot be cast to com.mysdk.JarEntryObject
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Router.doHandle(Router.java:422)
    at org.restlet.routing.Router.handle(Router.java:641)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.engine.application.StatusFilter.doHandle(StatusFilter.java:140)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.engine.CompositeHelper.handle(CompositeHelper.java:202)
    at org.restlet.engine.application.ApplicationHelper.handle(ApplicationHelper.java:77)
    at org.restlet.Application.handle(Application.java:385)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Router.doHandle(Router.java:422)
    at org.restlet.routing.Router.handle(Router.java:641)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.routing.Router.doHandle(Router.java:422)
    at org.restlet.routing.Router.handle(Router.java:641)
    at org.restlet.routing.Filter.doHandle(Filter.java:150)
    at org.restlet.routing.Filter.handle(Filter.java:197)
    at org.restlet.engine.CompositeHelper.handle(CompositeHelper.java:202)
    at org.restlet.Component.handle(Component.java:408)
    at org.restlet.Server.handle(Server.java:507)
    at org.restlet.engine.connector.ServerHelper.handle(ServerHelper.java:63)
    at org.restlet.engine.adapter.HttpServerHelper.handle(HttpServerHelper.java:143)
    at org.restlet.ext.servlet.ServerServlet.service(ServerServlet.java:1117)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:726)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:206)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:324)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:829)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:514)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)
quarks
  • 33,478
  • 73
  • 290
  • 513
  • 1
    There is another class loader here. You've got `JarByteClassloader` loading the class, and the default ClassLoader providing the compile-time `JarEntryObject`. – Paul Hicks Jul 24 '17 at 02:28
  • 1
    Does `JarByteClassloader` delegate to its parent? Does it *have* a parent? If not, @PaulHicks is correctly hinting at the problem. – Kevin Krumwiede Jul 24 '17 at 02:33
  • @KevinKrumwiede the class is like this: `public class JarByteClassloader extends ClassLoader` – quarks Jul 24 '17 at 02:35
  • @PaulHicks hmmm nice catch, what is the proper way to cast here that will not cause compile-time classloader to interfere? – quarks Jul 24 '17 at 02:37
  • I mean does it use [this](https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#ClassLoader(java.lang.ClassLoader)) superclass constructor or [this one](https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#ClassLoader())? – Kevin Krumwiede Jul 24 '17 at 02:37
  • Usually, if you know the class at compile time (and you do, since you're casting to `JarEntryObject`), then the correct thing is to _not_ use a new class loader. Just use the default one. `JarEntryObject jarEntry = new JarEntryObject();` – Paul Hicks Jul 24 '17 at 02:41
  • In this case, the class to load is `class MyJarEntryObject extends JarEntryObject` then basically it needs to be cast to `JarEntryObject` to access the methods since `MyJarEntryObject ` for this matter is not known at compile time. – quarks Jul 24 '17 at 02:45
  • I mean this code is part of a dynamic Jar module loading system. Jars dynamically loaded from a datastore. – quarks Jul 24 '17 at 02:48
  • So `classToLoad` is `MyJarEntryObject` which extends the `JarEntryObject` provided by the default class loader? If that's the case, then your problem is what @KevinKrumwiede said: you need to ensure that all constructors in `JarByteClassloader` call `super(ordinaryObject.getClass().getClassLoader())`. – Paul Hicks Jul 24 '17 at 02:50

1 Answers1

3

It looks like your JarByteClassloader is not joining the default class loader's hierarchy. Ensure that the constructor(s) call super(ClassLoader). Something like this might work:

public class JarByteClassloader extends ClassLoader {
  public JarByteClassloader(Object objectLoadedByDefaultClassLoader) {
    super(objectLoadedByDefaultClassLoader.getClass().getClassLoader());
  }
}

And since you're already passing in a jar that was loaded by the default class loader, you're done!

Actually, I've just remember getSystemClassLoader(), so this code is probably better, and more self-documenting:

public class JarByteClassloader extends ClassLoader {
  public JarByteClassloader() {
    super(ClassLoader.getSystemClassLoader());
  }
}
Paul Hicks
  • 13,289
  • 5
  • 51
  • 78
  • What could be the reason that even not doing this code you shared when running with the static `main` function the code works fine? – quarks Jul 24 '17 at 04:31
  • BTW the JarByteClassloader is very similar to the class loader here: https://stackoverflow.com/questions/16602668/creating-a-classloader-to-load-a-jar-file-from-a-byte-array – quarks Jul 24 '17 at 04:48
  • And adding the super method doesn't fix. – quarks Jul 24 '17 at 04:53
  • 1
    @xybrek the class com.mysdk.JarEntryObject is loaded twice: once by the JarByteClassloader created to load com.mywebapp.example.MyJarEntryObject, and once by the class loader used to run the program. The two instances of com.mysdk.JarEntryObject are not compatible. – Maurice Perry Jul 24 '17 at 08:31
  • The reason things work differently in the app container and the standard JVM is likely because the system class loader does different things: the the app container, every app gets its own system class loader; in JUnit / command-line app, there's only one system class loader. That's just a guess though. – Paul Hicks Jul 24 '17 at 20:32
  • Does neither super method option work? That is, `jar.getClass().getClassLoader()` and `ClassLoader.getSystemClassLoader()` both don't help? – Paul Hicks Jul 24 '17 at 20:33