Revisiting my spotviz.info webapp: visualizing WSJT-X FT8 spots over time – part 2: jaxb with Java 9 and beyond

Continuing from my efforts to get my SpotViz.info site up and running again on a current version of WildFly before refreshing the tech used to run the app, I ran into the first major issue: jaxb and support on Java 9 and beyond.

This is the first time I’ve run into this in practice, but was aware of the move to remove jaxb from Java SE in releases after Java 9. Deploying and running the app locally on WildFly17 and Java 8 everything deploys and works unchanged, but now I’m deploying to a test Ubuntu18.04 server in a VM on my local rack server before moving to the cloud, I’m running into all sorts of exceptions at runtime with part of the app that uses jax-ws and jaxb to call a remote xml based webservice.

At runtime as the MDB is picking up messages to process from the queue, I’m getting this exception:

Caused by: java.lang.NullPointerException
at org.jboss.resteasy.resteasy-cdi@3.7.0.Final//org.jboss.resteasy.cdi.CdiInjectorFactory.createConstructor(CdiInjectorFactory.java:91)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.spi.ResteasyProviderFactory.injectedInstance(ResteasyProviderFactory.java:2809)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry$AbstractInterceptorFactory.createInterceptor(JaxrsInterceptorRegistry.java:170)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry$OnDemandInterceptorFactory.initialize(JaxrsInterceptorRegistry.java:188)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry$OnDemandInterceptorFactory.checkInitialize(JaxrsInterceptorRegistry.java:203)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry$OnDemandInterceptorFactory.getInterceptor(JaxrsInterceptorRegistry.java:214)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry$AbstractInterceptorFactory.postMatch(JaxrsInterceptorRegistry.java:158)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.core.interception.JaxrsInterceptorRegistry.postMatch(JaxrsInterceptorRegistry.java:421)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientConfiguration.getRequestFilters(ClientConfiguration.java:112)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.getRequestFilters(ClientInvocation.java:408)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.filterRequest(ClientInvocation.java:582)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:440)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:464)
at org.jboss.resteasy.resteasy-jaxrs@3.7.0.Final//org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder.get(ClientInvocationBuilder.java:189)
at deployment.callsignviz-1.0.war//kh.hamqthclient.HamQTHClient.logon(HamQTHClient.java:70)
at deployment.callsignviz-1.0.war//kh.callsign.spotcollector.service.CallsignProcessorService.(CallsignProcessorService.java:35)

The clue here is the NullPointerException is coming from the HamQTHClient.logon() call, which is my library using jax-ws to call the remote HamQTH service.

Running my JUnits locally, this is definitely a JDK version issue. On Java 8 this code runs fine, but on Java 10 and 11 I get this:

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.PropertyException
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)

… and now I’m remembering the jax-ws removal timeline from JEP-320:

  • Java 9 : deprecated for future removal
  • Java 11: complete removal of javax.xml.bind (jaxb) and javax.xml.ws (jax-ws) (also see here)

I don’t currently have Java 9 installed locally, but I suspect the code would work, but my test local Ubuntu 18.04 has OpenJDK 11, so this is currently my target JRE.

Ok, so what are the migration steps for using jax-ws after Java 8? A quick search found this useful article on migration, and adding explicit dependencies on the jaxb-api and the Glassfish reference implementation was exactly what I needed to get the code to run unmodified on Java 11:

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>

<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>

 

JAX-WS endpoint deployment issue on OpenShift WildFly 8.2

When attempting to deploy an app with a JAX-WS endpoint to WildFly on OpenShift, attempting to hit the ?wsdl url to check the generated wsdl gives this error:

22:49:49,502 ERROR [io.undertow.request] (default task-3) UT005023: Exception handling request to /ExampleEndpoint: javax.xml.ws.WebServiceException: JBWS024032: Cannot obtain endpoint jboss.ws:context=,endpoint=example.endpoint.ExampleEndpoint at org.jboss.wsf.stack.cxf.transport.ServletHelper.initServiceEndpoint(ServletHelper.java:82)

From some Googling, it appears this issue is related to the fact that on OpenShift your app is deployed as the root context and so you need to add a jboss-web.xml to define that your app is deployed at the root context (and not at /ROOT/, since the deployed app is ROOT.war) so your wsdl can be found at the expected url.

Updating the endpoint URL for a generated JAX-WS client

When you generate a JAX-WS client from a locally deployed service, the URL for the endpoint and the WSDL are hardcoded into the generated client code. If you redeploy the service elsewhere (i.e. moving from a local development environment to a test or production environment), then you could regenerate against the new URL for the service, but the JAX-WS api does allow you to programmatically specify the endpoint and wsdl locations.

From this post, the key is to use the BindingProvider to specify a new value for BindingProvider.ENDPOINT_ADDRESS_PROPERTY:

BindingProvider bp = (BindingProvider)port;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://your_url/ws/sample");

By itself though, this gave me an additional exception as it appears the client it still attempting to locate the WSDL. To work around this, you can also pass the new WSDL url location when you instantiate the generated client (this is discussed here). Before using the BindingProvider snippet above, pass the updated urls into the client like this:

EndpointService service = new EndpointService(
    new URL("http://your-url-to-endpoint/Endpoint?wsdl"),
    new QName("http://namespace-of-endpoint-from-wsdl/",
						"EndpointService"));

SpotCollectorEndpoint endpoint = service.getSpotCollectorEndpointPort();