Supplying a different version of JAXB for JAX-WS in Java 1.6
Asked Answered
R

3

32

I have a third party jar that ships with a jaxb-impl.jar and includes it in its manifest classpath. The problem is, it seems as though supplying your own version of JAXB (regardless of which version it may be) seems to break the SoapFaultBuilder in JAX-WS.

According to the Unofficial JAXB Guide, it seems as though Sun deliberately changed the package name when it folded JAXB into the JDK in order to avoid conflicts with the stand-alone version. However, the SoapFaultBuilder (part of JAX-WS I believe) that ships with the JDK is explicitly dependent on the new, internal package name. This causes it to fail when building a fault message if you've added a stand-alone JAXB jar (even if it is the same version number of JAXB).

Here is my small test case: I make a trivial Web Service:

package wstest;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

//Service Endpoint Interface
@WebService
@SOAPBinding(style = Style.RPC)
public interface HelloWorld{

    @WebMethod String getHelloWorldAsString(String name);

}

And an implementation that simply throws an exception. (Since the problem only occurs in the SOAPFaultBuilder):

package wstest;

import javax.jws.WebService;

//Service Implementation
@WebService(endpointInterface = "wstest.HelloWorld")
public class HelloWorldImpl implements HelloWorld{

    @Override
    public String getHelloWorldAsString(String name) {
        //return "Hello World JAX-WS " + name;
        throw new RuntimeException("Exception for: " + name);
    }

}

And a class to publish the web service:

package wstest;

import javax.xml.ws.Endpoint;

//Endpoint publisher
public class HelloWorldPublisher{

    public static void main(String[] args) {
       Endpoint.publish("http://localhost:9999/ws/hello", new HelloWorldImpl());
    }

}

I run the HelloWorldPublisher, and then run this client against it:

package wstest;

import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;

public class HelloWorldClient{

    public static void main(String[] args) throws Exception {

    URL url = new URL("http://localhost:9999/ws/hello?wsdl");

        //1st argument service URI, refer to wsdl document above
    //2nd argument is service name, refer to wsdl document above
        QName qname = new QName("http://wstest/", "HelloWorldImplService");

        Service service = Service.create(url, qname);

        HelloWorld hello = service.getPort(HelloWorld.class);

        System.out.println(hello.getHelloWorldAsString("Matt"));

    }

}

This correctly spits out the exception that was thrown by the Web Service. However, when I add any version of jaxb-impl.jar, whether on the classpath or in the endorsed lib, I get this stack trace:

Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:107)
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:78)
    at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:107)
    at $Proxy19.getHelloWorldAsString(Unknown Source)
    at wstest.HelloWorldClient.main(HelloWorldClient.java:21)
Caused by: java.lang.ClassCastException: com.sun.xml.bind.v2.runtime.JAXBContextImpl cannot be cast to com.sun.xml.internal.bind.api.JAXBRIContext
    at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.<clinit>(SOAPFaultBuilder.java:533)
    ... 5 more

The exception occurs because com.sun.xml.bind.v2.runtime.JAXBContextImpl from my jaxb-impl extends com.sun.xml.bind.api.JAXBRIContext instead of com.sun.xml.internal.bind.api.JAXBRIContext (note the missing 'internal' sub-package in the package hierarchy).

Also according to the Unofficial JAXB Guide, they say you need to use endorsed lib in order to correctly override the version of JAXB. As it turns out though, SOAPFaultBuilder uses JAXBContext.newInstance() to search the classpath for a file named /META-INF/services/javax.xml.bind.JAXBContext, then manually load (and reflexively create) a JAXBContext based on the class name specified in the file. So it doesn't matter - classpath or endorsed lib gives you the same behavior.

One workaround is to add -Djavax.xml.bind.JAXBContext=com.sun.xml.internal.bind.v2.ContextFactory to the command line, which causes JAXBContext.newInstance() to ignore the /META-INF/services/javax.xml.bind.JAXBContext file on the classpath and manually specifies the built-in version of JAXB. The other workaround is to simply not specify your own JAXB and use the version built into the JDK, but it seems from the Unofficial JAXB Guide, that Sun designed this system to be able to handle supplying your own JAXB implementation. Has anyone been able to successfully supply a version of JAXB and still be able to successfully capture fault messages? (Everything runs fine for me as long as there are no faults generated by the Web Service).

Rf answered 4/1, 2013 at 17:46 Comment(0)
D
41

I was stuck on this problem but was able to use the "work around" listed in this forum Q&A by setting a system property like so:

System.setProperty("javax.xml.bind.JAXBContext", 
                   "com.sun.xml.internal.bind.v2.ContextFactory"); 
Delgado answered 1/7, 2013 at 16:5 Comment(0)
C
4

The crux of this issue is there is a change in the JAXB API, the runtime implementation you are attempting to use does not match the version of the JAXB API bundled with the JDK.

In order to use a different version, you should copy coresponding versions of jaxb-api.jar and jaxws-api.jar into an endorsed lib (e.g. %JAVA_HOME%\lib\endorsed).

A complete list of options is given in section 7.1.2 of the Unofficial JAXB Guide

It is a mistake to copy the implementation jars (e.g. jaxb-impl.jar) into endorsed lib, these should simply be on your classpath.

Also note that you can get into trouble if you attempt to use a newer version of jaxb without also including a compatible version of jaxws. This is because the old jaxws attempts to reference the old jaxb, so if you're changing one make sure you do both. (Stack-trace in package com.sun.xml.internal.ws implicates an old jax-ws implementation. Even the latest release of Java still ships with old version 1 jaxb and jaxws apis).

Carotenoid answered 4/4, 2013 at 6:16 Comment(5)
Thanks for your response. It has some good info, but I don't think it is entirely correct. It appears that you do have to override both JAXB and JAX-WS. But it does not appear to have anything to do with a version conflict, since (as I mentioned in the question) the issue occurs even when you try to supply your own JAXB jar that matches the version supplied by the JDK. The internal package name is not old. It was introduced by Sun to avoid conflicts with a user-supplied JAXB. (See the second bullet point of intro of JAXB Unofficial Guide.) Java 1.7 u17 still has this internal package name.Rf
{{com.sun.xml.internal.ws}} is the old JAXWS version, it has not been updated even in the latest Java release.Carotenoid
I have successfully reproduced your problem from your example and also fixed it simply by using the latest version of jaxws (with jaxws-api.jar added to lib/endorsed). I have also updated my answer for clarity. Let me know if you still have an issue and I can share my Eclipse workspace via github or similar.Carotenoid
I guess the main thing I am missing then, if it is truly a version conflict with new vs. old, then why do I get the exception when replacing the built-in version 2.1.10 with the stand-alone version 2.1.10?Rf
You are correct, the problem is not necessarily new vs. old but stand-alone vs. built-in. You are getting the error because you did not completely replace the built-in one. To do that you must have the new api jar loaded by the JVM bootloader ahead of rt.jarCarotenoid
B
3

another possible solution without modifying the system properties is described at API docu

https://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.6/api/javax/xml/bind/JAXBContext.html

you can place a jaxb.properties file inside of the package of your model class.

javax.xml.bind.context.factory=com.sun.xml.internal.bind.v2.ContextFactory
Baggywrinkle answered 21/12, 2016 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.