JBoss.org Community Documentation

3.2.2.1. ClassCastExceptions - I'm Not Your Type

A java.lang.ClassCastException results whenever an attempt is made to cast an instance to an incompatible type. A simple example is trying to obtain a String from a List into which a URL was placed:

ArrayList array = new ArrayList();
array.add(new URL("file:/tmp"));
String url = (String) array.get(0);

java.lang.ClassCastException: java.net.URL
at org.jboss.book.jmx.ex0.ExCCEa.main(Ex1CCE.java:16)

The ClassCastException tells you that the attempt to cast the array element to a String failed because the actual type was URL. This trivial case is not what we are interested in however. Consider the case of a JAR being loaded by different class loaders. Although the classes loaded through each class loader are identical in terms of the bytecode, they are completely different types as viewed by the Java type system. An example of this is illustrated by the code shown in Example 3.1, “The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders”.

package org.jboss.book.jmx.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * An example of a ClassCastException that
 * results from classes loaded through
 * different class loaders.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExCCEc
{
    public static void main(String[] args) throws Exception
    {
        ChapterExRepository.init(ExCCEc.class);

        String chapDir = System.getProperty("j2eechapter.dir");
        Logger ucl0Log = Logger.getLogger("UCL0");
        File jar0 = new File(chapDir+"/j0.jar");
        ucl0Log.info("jar0 path: "+jar0.toString());
        URL[] cp0 = {jar0.toURL()};
        URLClassLoader ucl0 = new URLClassLoader(cp0);
        Thread.currentThread().setContextClassLoader(ucl0);
        Class objClass = ucl0.loadClass("org.jboss.book.jmx.ex0.ExObj");
        StringBuffer buffer = new
            StringBuffer("ExObj Info");
        Debug.displayClassInfo(objClass, buffer, false);
        ucl0Log.info(buffer.toString());
        Object value = objClass.newInstance();
        
        File jar1 = new File(chapDir+"/j0.jar");
        Logger ucl1Log = Logger.getLogger("UCL1");
        ucl1Log.info("jar1 path: "+jar1.toString());
        URL[] cp1 = {jar1.toURL()};
        URLClassLoader ucl1 = new URLClassLoader(cp1);
        Thread.currentThread().setContextClassLoader(ucl1);
        Class ctxClass2 = ucl1.loadClass("org.jboss.book.jmx.ex0.ExCtx");
        buffer.setLength(0);
        buffer.append("ExCtx Info");
        Debug.displayClassInfo(ctxClass2, buffer, false);
        ucl1Log.info(buffer.toString());
        Object ctx2 = ctxClass2.newInstance();
        
        try {
            Class[] types = {Object.class};
            Method useValue =
                ctxClass2.getMethod("useValue", types);
            Object[] margs = {value};
            useValue.invoke(ctx2, margs);
        } catch(Exception e) {
            ucl1Log.error("Failed to invoke ExCtx.useValue", e);
            throw e;
        }
    }
}

Example 3.1. The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders


package org.jboss.book.jmx.ex0;

import java.io.IOException;
import org.apache.log4j.Logger;
import org.jboss.util.Debug;

/**
 * A classes used to demonstrate various class
 * loading issues
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExCtx
{
    ExObj value;
    
    public ExCtx() 
        throws IOException
    {
        value = new ExObj();
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new StringBuffer("ctor.ExObj");
        Debug.displayClassInfo(value.getClass(), buffer, false);
        log.info(buffer.toString());
        ExObj2 obj2 = value.ivar;
        buffer.setLength(0);
        buffer = new StringBuffer("ctor.ExObj.ivar");
        Debug.displayClassInfo(obj2.getClass(), buffer, false);
        log.info(buffer.toString());
    }

    public Object getValue()
    {
        return value;
    }

    public void useValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new
            StringBuffer("useValue2.arg class");
        Debug.displayClassInfo(obj.getClass(), buffer, false);
        log.info(buffer.toString());
        buffer.setLength(0);
        buffer.append("useValue2.ExObj class");
        Debug.displayClassInfo(ExObj.class, buffer, false);
        log.info(buffer.toString());
        ExObj ex = (ExObj) obj;
    }

    void pkgUseValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        log.info("In pkgUseValue");
    }
}
package org.jboss.book.jmx.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExObj
    implements Serializable
{
    public ExObj2 ivar = new ExObj2();
}
package org.jboss.book.jmx.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.9 $
 */
public class ExObj2 
    implements Serializable
{
} 

Example 3.2. The ExCtx, ExObj, and ExObj2 classes used by the examples


The ExCCEc.main method uses reflection to isolate the classes that are being loaded by the class loaders ucl0 and ucl1 from the application class loader. Both are setup to load classes from the output/jmx/j0.jar, the contents of which are:

[examples]$ jar -tf output/jmx/j0.jar
...
org/jboss/book/jmx/ex0/ExCtx.class
org/jboss/book/jmx/ex0/ExObj.class
org/jboss/book/jmx/ex0/ExObj2.class

We will run an example that demonstrates how a class cast exception can occur and then look at the specific issue with the example. See Appendix A, Book Example Installation for instructions on installing the examples accompanying the book, and then run the example from within the examples directory using the following command:

[examples]$ ant -Dchap=jmx -Dex=0c run-example
...
     [java] java.lang.reflect.InvocationTargetException
     [java]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     [java]     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
     [java]     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
                .java:25)
     [java]     at java.lang.reflect.Method.invoke(Method.java:585)
     [java]     at org.jboss.book.jmx.ex0.ExCCEc.main(ExCCEc.java:58)
     [java] Caused by: java.lang.ClassCastException: org.jboss.book.jmx.ex0.ExObj
     [java]     at org.jboss.book.jmx.ex0.ExCtx.useValue(ExCtx.java:44)
     [java]     ... 5 more

Only the exception is shown here. The full output can be found in the logs/jmx-ex0c.log file. At line 55 of ExCCEc.java we are invoking ExcCCECtx.useValue(Object) on the instance loaded and created in lines 37-48 using ucl1. The ExObj passed in is the one loaded and created in lines 25-35 via ucl0. The exception results when the ExCtx.useValue code attempts to cast the argument passed in to a ExObj. To understand why this fails consider the debugging output from the jmx-ex0c.log file shown in Example 3.3, “The jmx-ex0c.log debugging output for the ExObj classes seen”.

[INFO,UCL0] ExObj Info
org.jboss.book.jmx.ex0.ExObj(f8968f).ClassLoader=java.net.URLClassLoader@2611a7
..java.net.URLClassLoader@2611a7
....file:/Users/orb/proj/jboss/jboss-docs/jbossas/j2ee/examples/output/jmx/j0.jar
++++CodeSource: (file:/Users/orb/proj/jboss/jboss-docs/jbossas/j2ee/examples/output/
                 jmx/j0.jar <no signer certificates>)
Implemented Interfaces:
++interface java.io.Serializable(41b571)
++++ClassLoader: null
++++Null CodeSource
[INFO,ExCtx] useValue2.ExObj class
org.jboss.book.jmx.ex0.ExObj(bc8e1e).ClassLoader=java.net.URLClassLoader@6bd8ea
..java.net.URLClassLoader@6bd8ea
....file:/Users/orb/proj/jboss/jboss-docs/jbossas/j2ee/examples/output/jmx/j0.jar
++++CodeSource: (file:/Users/orb/proj/jboss/jboss-docs/jbossas/j2ee/examples/output/
                 jmx/j0.jar <no signer certificates>)
Implemented Interfaces:
++interface java.io.Serializable(41b571)
++++ClassLoader: null
++++Null CodeSource

Example 3.3. The jmx-ex0c.log debugging output for the ExObj classes seen


The first output prefixed with [INFO,UCL0] shows that the ExObj class loaded at line ExCCEc.java:31 has a hash code of f8968f and an associated URLClassLoader instance with a hash code of 2611a7, which corresponds to ucl0. This is the class used to create the instance passed to the ExCtx.useValue method. The second output prefixed with [INFO,ExCtx] shows that the ExObj class as seen in the context of the ExCtx.useValue method has a hash code of bc8e1e and a URLClassLoader instance with an associated hash code of 6bd8ea, which corresponds to ucl1. So even though the ExObj classes are the same in terms of actual bytecode since it comes from the same j0.jar, the classes are different as seen by both the ExObj class hash codes, and the associated URLClassLoader instances. Hence, attempting to cast an instance of ExObj from one scope to the other results in the ClassCastException.

This type of error is common when redeploying an application to which other applications are holding references to classes from the redeployed application. For example, a standalone WAR accessing an EJB. If you are redeploying an application, all dependent applications must flush their class references. Typically this requires that the dependent applications themselves be redeployed.

An alternate means of allowing independent deployments to interact in the presence of redeployment would be to isolate the deployments by configuring the EJB layer to use the standard call-by-value semantics rather than the call-by-reference JBoss will default to for components collocated in the same VM. An example of how to enable call-by-value semantics is presented in Chapter 11, EJBs on JBoss