javaspringwebspheredeserializationspring-remoting

Could not deserialize result from HTTP invoker remote service [...]; nested exception is java.lang.ClassNotFoundException:


I am receiving the following error only when a shared library is added to my application on an IBM WebSphere instance (version 8.5.xx). The goal is to move a lot of shared libraries (jars) between some applications to a shared library on the server in order to reduce the war sizes of the applications. From what I have seen though, it doesn't matter if the war file size is reduced or not, whenever this shared library is added, we are seeing the below errors.

org.springframework.remoting.RemoteAccessException: Could not deserialize result from HTTP invoker remote service [<URL>]; nested exception is java.lang.ClassNotFoundException: com.example.models.ExampleModel
    at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.convertHttpInvokerAccessException(HttpInvokerClientInterceptor.java:221)
    at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:153)
    at com.example.configuration.LoggingHttpInvokerProxyFactoryBean.invoke(LoggingHttpInvokerProxyFactoryBean.java:28)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy305.getSomeListOfObjects(Unknown Source)
...
...
...
Caused by: java.lang.ClassNotFoundException: com.example.models.ExampleModel
    at java.lang.Class.forNameImpl(Native Method)
    at java.lang.Class.forName(Class.java:403)
    at java.io.ClassCache$FutureValue.get(ClassCache.java:177)
    at java.io.ClassCache.get(ClassCache.java:148)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:834)
    at org.springframework.core.ConfigurableObjectInputStream.resolveClass(ConfigurableObjectInputStream.java:78)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2017)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1900)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2194)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1722)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2439)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2363)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2221)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1722)
    at java.io.ObjectInputStream.readObjectImpl(ObjectInputStream.java:540)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:475)
    at org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor.doReadRemoteInvocationResult(AbstractHttpInvokerRequestExecutor.java:291)
    at org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor.readRemoteInvocationResult(AbstractHttpInvokerRequestExecutor.java:242)
    at org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor.doExecuteRequest(HttpComponentsHttpInvokerRequestExecutor.java:248)
    at org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor.executeRequest(AbstractHttpInvokerRequestExecutor.java:137)
    at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.executeRequest(HttpInvokerClientInterceptor.java:202)
    at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.executeRequest(HttpInvokerClientInterceptor.java:184)
    at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:150)

The following is a similar example of what the model class looks like on a smaller scale. I primarily wanted to note that it does indeed implement Serializable (using the default serialVersionUID):

package com.example.models.ExampleModel;

public class ExampleModel implements Serializable {
    private static final long serialVersionUID = 1L;
    private String customerNumber = "";
}

Any debugging ideas even would be greatly appreciated at a minimum. I also wanted to note, that calling this remoting method works fine when the shared library isn't added to the application on the server, but doesn't seem to work when the shared library is added. Also, the shared library on the server does not have the jar that contains the class that cannot be found. The jar with the class that cannot be found is in my WEB-INF/lib folder and I have verified that to be installed on the server.


Solution

  • This looks like an issue of class loader hierarchy, in which classes loaded by an application (EAR) class loader are attempting to load classes packaged in the web module (WAR) loader. I believe the ObjectInputStream's resolveClass() method is using its caller's class loader for the failing class load, and I'm also assuming here that your Spring classes are among those in the shared library.

    In a Java EE application, the EAR and WAR have separate class loaders, with the EAR's loader serving as the parent. When a shared library is associated with an application in WebSphere, its contents are added to the EAR loader's class path. This is a problem if classes in the shared library need visibility to classes packaged in the WAR, because parent loaders cannot "see" their children. WebSphere allows you to associate a shared library with either an application or with one or more web modules within the application, and that changes which class loader the library's class path is appended to.

    Assuming I'm understanding the packaging and behavior of resolveClass correctly, I think the solution should be fairly simple - instead of associating the shared library with the application, associate it instead with the web module that contains the class that isn't being found (note that you'll need to remove the EAR-level association as well, or it'll just load the Spring classes from there anyway and the same problem will occur). That will get both the WAR paths and shared library paths into the same class loader, eliminating the hierarchy problem.

    Note that even if my assumptions are correct, that solution won't work if you selected the "use an isolated class loader" option for the shared library - that setting creates a separate class loader that operates as a sort-of-parent of the WAR/EAR it's associated with, so it still would not be able to see classes in the WAR even if it's associated with the WAR appropriately. If there are jars that require the isolated class loading behavior, you'll have to either relocate the WAR classes to the shared library or put the Spring stuff back into the WAR.