Reactive Web Service Client with JAX-WS

XML Web Services Are Not Dead

A decade or so ago, SOAP and WSDL (XML Web Services) were the hottest thing in enterprise software. Spurred on by all the large vendors including IBM, Microsoft, Oracle, and, at the time, Sun Microsystems, absolutely everything was going to expose WSDL endpoints and the world would be a better place for it. That didn't quite come to pass: the WS-* house of cards collapsed under its own weight, in the backlash XML became a dirty word, and REST+JSON took over as The One True Way™ of exposing web services (not without problems of its own).

As a Data Engineer, a big part of my job is to pave the way for Data Science to happen. That means bringing in data. In some cases, I find myself querying old XML Web Services. If they're plain WSDL with a good XML Schema for the message payloads and eschew all the added standards of the WS-* era, I don't even mind.

In Java, the standard way of accessing (and offering) XML Web Services is JAX-WS.

Reactive Programming

Functional Reactive Programming (FRP) is a fairly recent development in the way we write Java backend services. A full exploration of what it is and what it means is beyond the scope of this article. Dan Lew wrote a good introduction to FRP on his blog. The FRP model yields program code that's very clear about its intent:

connectToOrderStream()
    .flatMap(o -> addCustomerInformation(o.getCustomerId()))
    .flatMap(o -> addShippingInformation(o.getShippingAddress()))
    .doOnNext(this::sendOrderToFulfillmentCenter)
    .doOnNext(this::sendOrderConfirmationEmail);

In traditional, procedural Java code, the five major operations of this example would sit far apart from each other, surrounded by reams of logging, transformation and error handling code. That code still exists in the reactive model, but it's been pushed down into the helper methods to get the clean composition shown above. The composition itself takes care of some of the error handling paths.

FRP combines well with non-blocking IO (also known as Asynchronous IO) to create services that are not only clear about their intent, but also offer good performance, in particular, hardware utilization. Non-blocking IO isn't a new idea, but it was Nginx and Node.js that really put it on the map in Java land, and it took a while for the application frameworks to catch up.

In typical Java fashion, there's more than one implementation of FRP. The original implementation on the JVM was RxJava. Oracle added the Flow API to Java 9. More recently, there's Project Reactor, which is endorsed by Spring Framework in the new Spring WebFlux API.

My client has standardised on Spring Framework, so for the remainder of this article, I'm using Reactor. The three frameworks are fairly similar, and the code sample should translate easily.

JAX-WS and Non-Blocking IO

I can wsimport the WSDL file and have a working Web Service client in minutes. Blocking IO (synchronous) was the standard in Java at the time JAX-WS was introduced, and wsimport generates only a blocking client. We require a non-blocking one.

JAX-WS will generate a non-block client if you provide a binding customisation:

<?xml version="1.0"?>
<jaxws:bindings
        wsdlLocation="https://api.myservice.example.com/?wsdl"
        xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
    <!-- https://stackoverflow.com/a/43560882/49489 -->
    <jaxws:enableAsyncMapping>true</jaxws:enableAsyncMapping>
</jaxws:bindings>

If you save the snippet shown above as async_mapping.xml, you can pass it to wsimport in the -b parameter like this:

wsimport \
     -b async_mapping.xml \
     "https://api.myservice.example.com/?wsdl"

The generated code will now contain one blocking two non-blocking overloads for every WSDL operation. The generated code follows this structure:

public interface ExampleSoapPortType { 
    Output           operation(     Input request);
    Response<Output> operationAsync(Input request);
    Future<?>        operationAsync(Input request, AsyncHandler<Output> asyncHandler);
}

Each WSDL operation is turned into three methods:

  1. The first of the three is the original, blocking implementation.
  2. The second one, using Response<Type> is unsuitable for our needs. This is because the Response<Type> class extends java.util.concurrent.Future. Future, while indeed async, is a blocking API: if you get() its value, the calling thread is made to wait for the response to come in.
  3. The third one, using the AsyncHandler callback, is both asynchronous and non-blocking. This is the one we need. We can ignore its return value and rely exclusively on the AsyncHandler. This callback handler is invoked on a JAX-WS worker thread when the response has come in.

Bridging Non-Blocking JAX-WS and Project Reactor

The last piece of the puzzle is to create an AsyncHandler that fits into the Reactor programming model. This is done using Mono.create() (or Flux.create()) and a consumer of MonoSink (or FluxSink), as explained in the producer create section of the Reactor docs.

A reactive JAX-WS client call therefore looks like this:

class Demo {
    public Mono<Output> operation(Input input) {
        return Mono.create(sink ->
           portType.operation(input, outputFuture -> {
               try {
                   sink.success(outputFuture.get());
               } catch (Exception e) {
                   sink.error(e);
               }
           })
        );
    }
}

We can simplify that with a utility class:

import static com.godatadriven.jaxws.reactive.example.ReactorAsyncHandler.into;

class Demo {
    public Mono<Output> operation(Input input) {
        return Mono.create(sink -> portType.operation(input, into(sink)));
    }
}

The ReactorAsyncHandler Utility

Here's the entirety of that utility class:

package com.godatadriven.jaxws.reactive.example;

import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.ws.AsyncHandler;
import reactor.core.publisher.MonoSink;

/**
 * Bridge JAX-WS Async API to Reactor.
 *
 * <h2>Usage:</h2>
 *
 * <pre>
 *     Mono.create(sink -> endpoint.method(argument, ReactorAsyncHandler.into(sink));
 * </pre>
 */
public class ReactorAsyncHandler {
  public static <T> AsyncHandler<T> into(MonoSink<T> sink) {
    return res -> {
      try {
        sink.success(res.get(1, TimeUnit.MILLISECONDS));
      } catch (InterruptedException | ExecutionException | TimeoutException e) {
        sink.error(e);
      }
    };
  }
}

The Flux API can be supported in the same way.

Summary

XML Web Services still exist, although they're not as popular as they were in the past. JAX-WS is the standard tool to query them in Java. JAX-WS predates Reactor, which is Spring Framework's implementation of the Functional Reactive Programming model. The gap between JAX-WS and Reactor can be bridged using JAX-WS' async mode and a simple adapter provided in this article.

Stay up to date on the latest insights and best-practices by registering for the GoDataDriven newsletter.
Follow us for more of this