What else can throw a ClassCastException in java?

JavaCachingClasscastexception

Java Problem Overview


This is an interview question.

The interview is over, but this question is still on my mind.

I can't ask the interviewer, as I did not get the job.

Scenario:

  • put object of class C1 in to a cache with key "a"

Later code:

C1 c1FromCache = (C1) cache.get("a");

This code throws a ClassCastException.

What can the reasons be?

I said because someone else put another object with the same key and so overwrote it. I was told no, think of other possibilities.

I said maybe the jar defining class C1 was not available on this node (not sure if this would result in a class cast or a ClassNotFoundException, but I was grasping for any lead now. Then I said maybe wrong version of class? They said the same jar of class C1 is there in all nodes).

Edit/ Add Asked if the get was throwing the ClassCast but was told no. after that i told him my action to resolve such an issue would be to drop in a test jsp that would mimic the actions and put better logging (stack trace) after the exception. that was the 2nd part of the question (why and what would you do if this happened in production)

Does anyone else have any ideas about why a cache get would result in a cast issue?

Java Solutions


Solution 1 - Java

One reason could be that the part of the code inserting the object uses a different classloader than the code retrieving it.
An instance of a class can not be cast to the same class loaded by a different classloader.

Response to the edit:

> What would you do if this happened in production?

This generally happens when the reading and inserting modules each include the same jar containing C1.
Since most containers try the parent classloader first, and then the local classloader (the Parent first strategy), the common solution to the problem is to instead load the class in the closest common parent to the inserting and reading modules.
If you move the module containing the C1 class to the parent module, you force both submodules to get the class from the parent, removing any classloader differences.

Solution 2 - Java

The ClassCastException can occur if the same class was loaded by multiple different classloaders and instances of the classes are being shared between them.

Consider the following example hierarchy.

SystemClassloader <--- AppClassloader <--+--- Classloader1
                                         |
                                         +--- Classloader2

I think in general the following are true but custom classloaders can be written which stray from this.

  • Instances of classes loaded by SystemClassloader are accessible in any of the classloader contexts.
  • Instances of classes loaded by AppClassloader are accessible in any of the classloader contexts.
  • Instances of classes loaded by Classloader1 are not accessible by Classloader2.
  • Instances of classes loaded by Classloader2 are not accessible by Classloader1.

As mentioned a common scenario where this occurs is web app deployments where generally speaking AppClassloader closely resembles the classpath configured in the appserver and then the Classloader1 and Classloader2 represent the classpaths of the individually deployed web apps.

If multiple web apps deploy the same JARs/classes then the ClassCastException can occur if there is any mechanism for the web apps to share objects such as a cache or shared session.

Another similar scenario where this can occur is if the classes are loaded by the web app and instances of these classes are stored in the user session or cache. If the web app is redeployed then these classes are reloaded by a new classloader and attempting to access the objects from the session or cache will throw this exception.

One method of avoiding this issue in Production is to move the JARs higher up in the classloader hierarchy. So instead of including the same JAR in each web app it may work better to include these in the classpath of the appserver. By doing this the classes are loaded only a single time and are accessible by all web apps.

Another method of avoiding this is to operate only on the interfaces that the shared objects. The interfaces then need to be loaded higher up in the classloader hierarchy but the classes themselves do not. Your example of getting the object from the cache would be the same but the C1 class would be replaced by an interface that C1 implements.

Below is some sample code that can be run independently to recreate this scenario. It's not the most concise and there certainly may be better ways to illustrate it but it does throw the exception for the reasons mentioned above.

In a.jar package the following two classes, A and MyRunnable. These are loaded multiple times by two independent classloaders.

package classloadertest;

public class A {
	private String value;

	public A(String value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return "<A value=\"" + value + "\">";
	}
}

And

package classloadertest;

import java.util.concurrent.ConcurrentHashMap;

public class MyRunnable implements Runnable {
	private ConcurrentHashMap<String, Object> cache;
	private String name;

	public MyRunnable(String name, ConcurrentHashMap<String, Object> cache) {
		this.name = name;
		this.cache = cache;
	}

	@Override
	public void run() {
		System.out.println("Run " + name + ": running");
		
		// Set the object in the cache
		A a = new A(name);
		cache.putIfAbsent("key", a);

		// Read the object from the cache which may be differed from above if it had already been set.
		A cached = (A) cache.get("key");
		System.out.println("Run " + name + ": cache[\"key\"] = " + cached.toString());
	}
}

Independent of the classes above run the following program. It must not share a classpath with the above classes to ensure that they are loaded from the JAR file.

package classloadertest;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
	public static void run(String name, ConcurrentHashMap<String, Object> cache) throws Exception {
		// Create a classloader using a.jar as the classpath.
		URLClassLoader classloader = URLClassLoader.newInstance(new URL[] { new File("a.jar").toURI().toURL() });

		// Instantiate MyRunnable from within a.jar and call its run() method.
		Class<?> c = classloader.loadClass("classloadertest.MyRunnable");
		Runnable r = (Runnable)c.getConstructor(String.class, ConcurrentHashMap.class).newInstance(name, cache);
		r.run();
	}

	public static void main(String[] args) throws Exception {
		// Create a shared cache.
		ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<String, Object>();

		run("1", cache);
		run("2", cache);
	}
}

On running this the following output is displayed:

Run 1: running
Run 1: cache["key"] = <A value="1">
Run 2: running
Exception in thread "main" java.lang.ClassCastException: classloadertest.A cannot be cast to classloadertest.A
        at classloadertest.MyRunnable.run(MyRunnable.java:23)
        at classloadertest.Main.run(Main.java:16)
        at classloadertest.Main.main(Main.java:24)

I put the source up on GitHub as well.

Solution 3 - Java

And finally, someone hacked the String intern table for the string "a".

See an example of how it can be done here.

Solution 4 - Java

Well maybe because C1 is an abstract class, and the get function also returns on object(of a subclass of C1 of course) which was casted to C1 before returning?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontgkprogView Question on Stackoverflow
Solution 1 - JavaKeppilView Answer on Stackoverflow
Solution 2 - JavaEd PleseView Answer on Stackoverflow
Solution 3 - JavaOldCurmudgeonView Answer on Stackoverflow
Solution 4 - JavashiladityaView Answer on Stackoverflow