Java: Write once, run everywhere? Not quite.

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-1126AXIS-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.

Advertisements

One thought on “Java: Write once, run everywhere? Not quite.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s