Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JaxWS webservice client for Java 6 not working in Java 8

I have JaxWS web service client which has been running succesfully in Java 6 for years. Now when Java was upgraded to version 8, we are getting NullPointerException when getting port

java.lang.NullPointerException
    at com.sun.xml.internal.ws.client.ClientContainer$1.getResource(Unknown Source)
    at com.sun.xml.internal.ws.assembler.MetroConfigLoader.locateResource(Unknown Source)
    at com.sun.xml.internal.ws.assembler.MetroConfigLoader.locateResource(Unknown Source)
    at com.sun.xml.internal.ws.assembler.MetroConfigLoader.init(Unknown Source)
    at com.sun.xml.internal.ws.assembler.MetroConfigLoader.<init>(Unknown Source)
    at com.sun.xml.internal.ws.assembler.TubelineAssemblyController.getTubeCreators(Unknown Source)
    at com.sun.xml.internal.ws.assembler.MetroTubelineAssembler.createClient(Unknown Source)
    at com.sun.xml.internal.ws.client.Stub.createPipeline(Unknown Source)
    at com.sun.xml.internal.ws.client.Stub.<init>(Unknown Source)
    at com.sun.xml.internal.ws.client.Stub.<init>(Unknown Source)
    at com.sun.xml.internal.ws.client.Stub.<init>(Unknown Source)
    at com.sun.xml.internal.ws.client.sei.SEIStub.<init>(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.getStubHandler(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.createEndpointIFBaseProxy(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(Unknown Source)
    at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(Unknown Source)
    at javax.xml.ws.Service.getPort(Unknown Source)
    at myclient.stub.MyService.<init>(MyService.java:38) 

I've tried running it with Java 7 version 1.7.0_80 and there it also works but very first version of Java 8 causes this exception.

I've been banging my head with this for quite some time now so if someone could give me any clues where to start fixing this it would be really great.

Here's the WSDL, I edited it a little bit since it's not my service but hopefully it's enough?

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
<wsdl:definitions xmlns:ns1="http://www.dummyservice/sample/interface" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Sample"
        targetNamespace="http://www.dummyservice/sample/interface">
  <wsdl:types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    targetNamespace="http://www.dummyservice/sample/interface" xmlns="http://www.dummyservice/sample/interface"
    elementFormDefault="qualified" jaxb:version="2.0">
      <xs:element name="PersonQuery">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="system" type="xs:string" />
            <xs:element name="user" type="xs:string" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="PersonReply">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="Header" type="HeaderType" />
            <xs:element name="person" type="PersonType" minOccurs="0" maxOccurs="1" />
            <xs:element name="address" type="AddressType" minOccurs="0" maxOccurs="unbounded" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:complexType name="HeaderType">
        <xs:sequence>
          <xs:element name="tila" type="StatusType" />
        </xs:sequence>
      </xs:complexType>
      <xs:simpleType name="StatusType">
        <xs:annotation>
          <xs:appinfo>
            <jaxb:typesafeEnumClass>
              <jaxb:typesafeEnumMember name="SUCCESS" value="0001" />
              <jaxb:typesafeEnumMember name="FAIL" value="0000" />
            </jaxb:typesafeEnumClass>
          </xs:appinfo>
        </xs:annotation>
        <xs:restriction base="xs:string">
          <xs:enumeration value="0000" />
          <xs:enumeration value="0001" />
        </xs:restriction>
      </xs:simpleType>
      <xs:complexType name="PersonType">
        <xs:sequence>
          <xs:element name="firstname" type="xs:string" minOccurs="0" />
          <xs:element name="lastname" type="xs:string" minOccurs="0" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="AddressType">
        <xs:sequence>
          <xs:element name="addresstype" type="AddresstypeType" minOccurs="0" />
          <xs:element name="streetaddress" type="xs:string" minOccurs="0" />
          <xs:element name="city" type="xs:string" minOccurs="0" />
          <xs:element name="postalcode" type="xs:string" minOccurs="0" />
        </xs:sequence>
      </xs:complexType>
      <xs:simpleType name="AddresstypeType">
        <xs:annotation>
          <xs:appinfo>
            <jaxb:typesafeEnumClass>
              <jaxb:typesafeEnumMember name="HOME" value="001" />
              <jaxb:typesafeEnumMember name="OFFICE" value="002" />
            </jaxb:typesafeEnumClass>
          </xs:appinfo>
        </xs:annotation>
        <xs:restriction base="xs:string">
          <xs:enumeration value="001" />
          <xs:enumeration value="002" />
        </xs:restriction>
      </xs:simpleType>
    </xs:schema>
  </wsdl:types>
  <wsdl:message name="PersonQueryOperationRequest">
    <wsdl:part element="ns1:PersonQuery" name="parameters" />
  </wsdl:message>
  <wsdl:message name="PersonQueryOperationResponse">
    <wsdl:part element="ns1:PersonReply" name="parameters" />
  </wsdl:message>
  <wsdl:portType name="SamplePort">
    <wsdl:operation name="PersonQueryOperation">
      <wsdl:input message="ns1:PersonQueryOperationRequest" />
      <wsdl:output message="ns1:PersonQueryOperationResponse" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="SampleSOAP" type="ns1:SamplePort">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="PersonQueryOperation">
      <soap:operation soapAction="http://www.dummyservice/sample/interface/SampleOperation" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="SampleService">
    <wsdl:port binding="ns1:SampleSOAP" name="Sample">
      <soap:address location="https://127.0.0.1/data/ws" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Edit: Class loading seems to be the issue, both context class loader and class' class loader are null in that ClientContainer.

private final ResourceLoader loader = new ResourceLoader() {
public URL More ...getResource(String resource) throws MalformedURLException {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
        cl = this.getClass().getClassLoader();
    }
    return cl.getResource("META-INF/"+resource);
}
};

When we explicitly set system class loader to context class loader before invoking ws call it started working. But is that good fix for this? I'm wondering why this has stopped working in Java8 and could this be an issue in their ws-implementation?

Regards,

Janne

like image 627
Janne Rantala Avatar asked Sep 22 '15 15:09

Janne Rantala


3 Answers

We have been seeing this problem (same stack trace and all) after upgrading from Java 6 to Java 8 as well. I will post our solution first, followed by additional explanation.

For us, the main conditions under which this problem occurs is:

  1. Java 8 running as an embedded JVM via jvm.dll from within a Windows program. I tried to reproduce the problem running in a standalone Java 8 JVM and I couldn't get it to happen. Running standalone was very helpful in diagnosing the problem, however.
  2. Using the proxy method of initializing the connection to a JAX-WS web service, which dynamically creates the stub on the client side using the WSDL hosted on the server side.

Workaround:

When you invoke the call to Service.getPort(Class<T>) it needs to be done in its own thread, separate from the thread you had the call running in previously. What this allows for you is an opportunity to set the ClassLoader on that thread to a ClassLoader that is not the bootstrap classloader, which is the crux of the problem in ClientContainer.java (more explanation on that below). Here is some sample code that is working for us. You will have to modify it to suit your needs.

public class YourClass {
  private YourWebService yourWebService;

  // You may want to synchronize on this method depending on your use case
  public YourWebService getYourWebService() {
    if ( this.yourWebService == null ) {
      // We create a thread so that we can set the ClassLoader
      Thread t = new Thread() {
        public void run() {
          synchronized(this) {
            // Get the string of your webservice WSDL from somewhere
            String url = "http://YOURHOST:YOURPORT/your-web-service/YourWebService?wsdl";
            URL srvUrl = null;
            try {
              srvUrl = new URL( url );
            } catch ( MalformedURLException ex ) {
              throw new RuntimeException( String.format( "Malformed URL: %s", url ), ex );
            }

            QName qName = new QName( "your-webservice-namespace", "YourWebServiceName" );
            Service service = Service.create( srvUrl, qName );
            this.yourWebService = service.getPort( YourWebService.class );
            notify();
          }
        }
      };

      // Thread.currentThread().getContextClassloader()
      // returns null in com.sun.xml.internal.ws.client.ClientContainer.
      // (See http://hg.openjdk.java.net/jdk8/jdk8/jaxws/file/d03dd22762db/src/share/jaxws_classes/com/sun/xml/internal/ws/client/ClientContainer.java, lines 39-47)
      // To work around that, I force setting of the ContextClassLoader 
      // on this thread (in which the Service.getPort() method will run) so 
      // that when ClientContainer calls Thread.currentThread().getContextClassLoader(), it doesn't get a null 
      // (i.e., the bootstrap classloader).
      //
      t.setContextClassLoader( YourClass.class.getClassLoader() );
      t.start();
      // Wait until above thread completes in order to return yourWebService 
      synchronized( t ) {
        try {
          t.wait();
        } catch ( InterruptedException e ) {
          e.printStackTrace();
        }
      }
    }

    return this.yourWebService;
  }
} 

Additional Background and Details:

The difficulty in diagnosing this for us was that the problem only occurred inside of a Windows product which launches an embedded JVM. Without remote debugging on that JVM, it would have taken much longer to get to the bottom of the problem. Once I saw that 1) when I ran the same code which invokes the call to Service.getPort(Class<T>) inside of a standalone JVM (outside of the Windows product) and, 2) that the ClientContainer class was able to get the current thread's ClassLoader and, 3) that the ClassLoader returned wasn't the bootstrap ClassLoader (i.e., not null), it made me realize that I had to find a way to ensure that the thread that the ClientContainer was running in would not get the bootstrap ClassLoader. The goal then became to see if I could find a way to alter the ClassLoader resolved by the ClientContainer code.

ClientContainer source: http://hg.openjdk.java.net/jdk8/jdk8/jaxws/file/d03dd22762db/src/share/jaxws_classes/com/sun/xml/internal/ws/client/ClientContainer.java

Notice in the ClientContainer source that there are two attempts to resolve a classloader. The problem is that if both of those attempts return the bootstrap classloader, a NullPointerException will result on line 45 since cl will be null:

cl.getResource("META-INF/"+resource);

This workaround ensures that the classloader resolved by the ClientContainer code will be the classloader that you set on your thread.

I've filed a ticket for the JAX-WS team to investigate the problem here: original java.net link now on GitHub #1178.

like image 127
2Aguy Avatar answered Nov 02 '22 11:11

2Aguy


I came across the same problem after upgrading to Java 8. @2AGuy's answer helps a lot. However, the problem took place both running on Mac and Linux for me. And there may be a simpler version to solve the issue. Your code can execute below line before calling getPort():

Thread.currentThread().setContextClassLoader(XXX.class.getClassLoader());

This will replace the thread context class loader from Bootstrap class loader to whomever you set it to be, without the overhead to create a new thread just to set the classLoader.

The root cause of this is still a mystery to me. My suspicion is that the thread to create a SOAP service client proxy (a Service Endpoint Proxy in documentation context) is the main thread or created by it, whose classLoader is the bootstrap class loader, being loaded in the earliest stage when JVM starts. Based on Java doc for Class#getClassLoader(), a NullPointer appears.

Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.

like image 41
Cady Avatar answered Nov 02 '22 13:11

Cady


Try generating the stubs for the webservice client again in Java8.

Generally we generate a jar of stubs and put it in the classpath. Given that Java is backward compatible we skip this step of regenerating the jar when upgrading to a higher version.

But since for Java 6 & 7 its working and only failing in Java 8, I would recommend generating the Stubs again just to rule out any glitch in Java 8's backward compatibility.

like image 29
Dhruv Rai Puri Avatar answered Nov 02 '22 11:11

Dhruv Rai Puri