Cache in Data Center throws ClassCastException when casting to the same class

Hi guys,

I’m trying to use cache in JIRA Data Center but I’m getting this error:

java.lang.ClassCastException: com.softwareplant.util.lang.POJO cannot be cast to com.softwareplant.util.lang.POJO
	at com.softwareplant.datacenter.DataCenterService.getAllJIRAS(DataCenterService.java:67)

This error occurs when I’m iterating through values in my cache:

Collection<String> keys = jiraCache.getKeys();
for (String key : keys) {
	POJO pojoValue = jiraCache.get(key);
	log.info("\nKEY = {}, VALUE = {} ", key, pojoValue);
}

Cache configuration is very simple and it looks like this:

CacheSettings settings =new CacheSettingsBuilder().
	remote().replicateViaCopy().
	expireAfterAccess(6000, TimeUnit.MINUTES).
	build();

com.atlassian.cache.Cache<String, POJO> jiraCache= cacheManager.getCache("my-cache", null, settings);

POJO is my own class which implements Serializable interface and have two String fields - .

I read these articles 1 and 2.
I added my class to application classpath but I end up with ClassCastException :frowning:

Do you have any ideas how should I setup my cache/application/datacenter to resolve this issue?

2 Likes

It is good to mention that we ended up with “no security manager: RMI class loader disabled” if a class was not added to the application classpath.

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
	java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
	java.lang.ClassNotFoundException: com.softwareplant.util.lang.POJO (no security manager: RMI class loader disabled)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:355)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:276)
	at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:253)
	at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:162)
	at net.sf.ehcache.distribution.RMICachePeer_Stub.remove(Unknown Source)
	at net.sf.ehcache.distribution.RMISynchronousCacheReplicator.replicateRemovalNotification(RMISynchronousCacheReplicator.java:243)
	at net.sf.ehcache.distribution.RMISynchronousCacheReplicator.notifyElementRemoved(RMISynchronousCacheReplicator.java:229)
	at net.sf.ehcache.event.RegisteredEventListeners.internalNotifyElementRemoved(RegisteredEventListeners.java:144)
	at net.sf.ehcache.event.RegisteredEventListeners.notifyElementRemoved(RegisteredEventListeners.java:124)
	at net.sf.ehcache.Cache.notifyRemoveInternalListeners(Cache.java:2322)
	at net.sf.ehcache.Cache.removeInternal(Cache.java:2305)
	at net.sf.ehcache.Cache.remove(Cache.java:2207)
	at net.sf.ehcache.Cache.remove(Cache.java:2125)
	at net.sf.ehcache.constructs.EhcacheDecoratorAdapter.remove(EhcacheDecoratorAdapter.java:154)
	at com.atlassian.cache.ehcache.LoadingCache.remove(LoadingCache.java:208)
	at com.atlassian.cache.ehcache.DelegatingCache.remove(DelegatingCache.java:134)

In our experience, relying on Java object serialization may lead to a plethora of problems. You may want to consider serializing to some alternative format, like JSON.

Your original problem that caused this exception:

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
	java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
	java.lang.ClassNotFoundException: com.softwareplant.util.lang.POJO (no security manager: RMI class loader disabled)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:355)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)

is caused by the fact during cache replication in Data Center RMI uses webapp class loader to deserialize cache invalidation message and not OSGi class loader associated with your plugin’s OSGi bundle.

The easiest way to work around this problem is to use keys that can be serialized/deserialized by webapp class loader (e.g. java.lang.String). (As long as you use loading cache and do not call put() on it you should be fine with using your own classes as cache values).

I’ve created a bug that you can watch: https://jira.atlassian.com/browse/JRASERVER-65246

1 Like

The Atlassian guide on using Data Centre Compatible caches here says the cache can contain keys and values that implement Serializable however from what I have seen serializing pojos does not work as there are different classloaders in play which results in ClassCastException.

I side stepped this problem by having local caches on each node but using a single key value pair in the Atlassian Data Centre Cache to ensure local caches were up to date. I stored the Java epoch time in the Data Centre cache. Each node checks this, if it is the same as its local copy its local cache is safe to use. If its changed the local cache is destroyed and rebuilt with the local epoch time being updated.