[Note: I originally posted this solution to Stackoverflow.com]
Problem:
When connecting to a SOAP based web service from a Java 8 embedded JVM using JAX-WS dynamic generation of the client stubs, you get this NullPointerException:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Caused by: java.lang.NullPointerException at com.sun.xml.internal.ws.client.ClientContainer$1.getResource(ClientContainer.java:45) at com.sun.xml.internal.ws.assembler.MetroConfigLoader.locateResource(MetroConfigLoader.java:220) at com.sun.xml.internal.ws.assembler.MetroConfigLoader.locateResource(MetroConfigLoader.java:230) at com.sun.xml.internal.ws.assembler.MetroConfigLoader.init(MetroConfigLoader.java:125) at com.sun.xml.internal.ws.assembler.MetroConfigLoader.<init>(MetroConfigLoader.java:104) at com.sun.xml.internal.ws.assembler.TubelineAssemblyController.getTubeCreators(TubelineAssemblyController.java:78) at com.sun.xml.internal.ws.assembler.MetroTubelineAssembler.createClient(MetroTubelineAssembler.java:103) at com.sun.xml.internal.ws.client.Stub.createPipeline(Stub.java:328) at com.sun.xml.internal.ws.client.Stub.<init>(Stub.java:295) at com.sun.xml.internal.ws.client.Stub.<init>(Stub.java:228) at com.sun.xml.internal.ws.client.Stub.<init>(Stub.java:243) at com.sun.xml.internal.ws.client.sei.SEIStub.<init>(SEIStub.java:84) at com.sun.xml.internal.ws.client.WSServiceDelegate.getStubHandler(WSServiceDelegate.java:814) at com.sun.xml.internal.ws.client.WSServiceDelegate.createEndpointIFBaseProxy(WSServiceDelegate.java:803) at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:436) at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:404) at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:459) at com.sun.xml.internal.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:463) at javax.xml.ws.Service.getPort(Service.java:188) |
If your problem is the same as mine, you will also have the symptom that this problem did not occur under Java 6, but now all of a sudden is happening under Java 8.
Solution:
For me, the main conditions under which this problem occurs is:
- 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.
- 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 me. You will have to modify it to suit your needs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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 me 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: click to view
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:
45 |
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: https://java.net/jira/browse/JAX_WS-1178
By Thanks a lot February 1, 2021 - 2:19 pm
This Workaround is a few years old, but works still perfect.
Thanks a lot!
BTW: The link below (https://java.net/jira/browse/JAX_WS-1178) is not available anymore.
By Thommy February 1, 2021 - 2:25 pm
This Workaround is a few years old, but works still perfect.
Thanks a lot!
BTW: The link below (https://java.net/jira/browse/JAX_WS-1178) is not available anymore.