Java: Write once, run everywhere? Not quite. At least not with older versions of Apache CXF, an otherwise fantastic SOAP framework I use in a server side Java 7 app server.
My trouble with the Java Tribbles started when a customer tried to run our proprietary server on an IBM iSeries a.k.a AS/400. IBM has its own JVM called J9 on the 400.No, it’s not a Java 9 preview JVM, it’s just a confusing name, so thanks for that 😉
The first furry incident revealed itself as:
java.lang.NoClassDefFoundError: com/sun/xml/messaging/saaj/soap/SOAPDocumentImpl
at com.sun.xml.messaging.saaj.soap.SOAPPartImpl.<init>(SOAPPartImpl.java:106)
at com.sun.xml.messaging.saaj.soap.ver1_1.Message1_1Impl.getSOAPPart(Message1_1Impl.java:90)
at org.apache.cxf.binding.soap.saaj.SAAJInInterceptor$SAAJPreInInterceptor.handleMessage(SAAJInInterceptor.java:131)
at org.apache.cxf.binding.soap.saaj.SAAJInInterceptor$SAAJPreInInterceptor.handleMessage(SAAJInInterceptor.java:101)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:262)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:122)
at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:211)
at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:213)
at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:154)
at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:129)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:187)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:110)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:166)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:457)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1075)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:384)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1009)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:255)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:368)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489)
at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:953)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1014)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:861)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:628)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:761)
Here we see Jetty running a custom servlet which extends the Apache CXF 2.6.0 AbstractHTTPServlet. I do most of my development on Windows with some Linux testing, all using Oracle JVMs. Oracle bundles all sorts of extra code under the com.sun package which are not part of the Java standard. It’s no surprise then to see an error when the IBM java runtime tries to load com/sun/xml/messaging/saaj/soap/SOAPDocumentImpl.
It turns our there is a workaround for this known problem. You need to set the following system properties:
javax.xml.soap.MessageFactory = com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl
javax.xml.soap.SOAPFactory = com.sun.xml.internal.messaging.saaj.soap.ver1_1.SOAPFactory1_1Impl
javax.xml.soap.SOAPConnectionFactory = com.sun.xml.internal.messaging.saaj.client.p2p.HttpSOAPConnectionFactory
javax.xml.soap.MetaFactory = com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl
See also OpenEJB-1126, AXIS-4228 and Customizing your IBM i server for Java usage.
Done? Nope. What happens next is painful:
java.lang.NoClassDefFoundError: com.sun.org.apache.xerces.internal.dom.DocumentImpl
at java.lang.ClassLoader.defineClassImpl(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:287)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:74)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:540)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:451)
at java.net.URLClassLoader.access$300(URLClassLoader.java:79)
at java.net.URLClassLoader$ClassFinder.run(URLClassLoader.java:1038)
at java.security.AccessController.doPrivileged(AccessController.java:365)
at java.net.URLClassLoader.findClass(URLClassLoader.java:429)
at java.lang.ClassLoader.loadClass(ClassLoader.java:677)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:358)
at java.lang.ClassLoader.loadClass(ClassLoader.java:643)
at com.sun.xml.messaging.saaj.soap.SOAPPartImpl.<init>(SOAPPartImpl.java:106)
at com.sun.xml.messaging.saaj.soap.ver1_1.Message1_1Impl.getSOAPPart(Message1_1Impl.java:90)
at org.apache.cxf.binding.soap.saaj.SAAJInInterceptor$SAAJPreInInterceptor.handleMessage(SAAJInInterceptor.java:131)
at org.apache.cxf.binding.soap.saaj.SAAJInInterceptor$SAAJPreInInterceptor.handleMessage(SAAJInInterceptor.java:101)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:262)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:122)
at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:211)
at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:213)
at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:154)
at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:129)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:187)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:110)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:755)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:166)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:669)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:457)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1075)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:384)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1009)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:255)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:364)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489)
at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:953)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1014)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:861)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:628)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:761)
Caused by: java.lang.ClassNotFoundException: com.sun.org.apache.xerces.internal.dom.DocumentImpl
at java.net.URLClassLoader.findClass(URLClassLoader.java:434)
at java.lang.ClassLoader.loadClass(ClassLoader.java:677)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:358)
at java.lang.ClassLoader.loadClass(ClassLoader.java:643)
... 46 more
Helmet on? Check. Light on? Check. The class com.sun.xml.messaging.saaj.soap.SOAPPartImpl lives in saaj-impl-1.3.18.jar which is delivered with CXF 2.6.0. What happens at line 106? Nothing dramatic (the listing starts at line 105):
protected SOAPPartImpl(MessageImpl message) {
document = new SOAPDocumentImpl(this);
headers = new MimeHeaders();
this.message = message;
headers.setHeader("Content-Type", getContentType());
}
Let’s take a look at SOAPDocumentImpl then:
package com.sun.xml.messaging.saaj.soap;
import java.util.logging.Logger;
import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
import org.w3c.dom.*;
import com.sun.xml.messaging.saaj.soap.impl.*;
import com.sun.xml.messaging.saaj.soap.name.NameImpl;
import com.sun.xml.messaging.saaj.util.LogDomainConstants;
public class SOAPDocumentImpl extends DocumentImpl implements SOAPDocument {
...
And there you have it, in a nutshell: SOAPDocumentImpl extends com.sun.org.apache.xerces.internal.dom.DocumentImpl. SAAJ 1.3.18 is hard-wired to Sun’s internal copy of Apache Xerces. Blearch!
Luckily for us and our customer, the solution was fairly painless: We can easily drop in a later version of our server which uses Apache CXF 2.7.5 instead of 2.6.0, a version which no longer relies on the SAAJ jar file. This also eliminates the need for the system properties hack mentioned above.