Introducing MicroProfile Rest Client 1.0

The Liberty application server now offers the mpRestClient-1.0 feature. This is the implementation of the MicroProfile Rest Client 1.0 API spec released in December 2017.

The MicroProfile Rest Client builds on JAX-RS 2.0 client APIs to provide a type-safe approach for invoking RESTful services. This means writing client applications with more model-centric code and less 'plumbing'. Although these are client APIs, they are deployed in a server.

Here is a quick run-down of how to create your own REST clients:

The interface

First, we start off with an interface that represents the remote service. The methods of the interface should match the RESTful APIs of the endpoint. So if we want to access a service that provides online music and allows us to view and modify playlists, we might create an interface that looks like this:

@Path("/playlist")
@Consumes("application/json")
public interface MusicPlaylistService {

    @GET
    List<String> getPlaylistNames();

    @GET
    @Path("/{playlistName}")
    List<Song> getPlaylist(@PathParam("playlistName") String name)
        throws UnknownPlaylistException;

    @POST
    @Path("/{playlistName}")
    long newPlayList(@PathParam("playlistName") String name, List<Song> playlist)
        throws PlaylistAlreadyExistsException;

    @PUT
    @Path("/{playlistName}")
    long updatePlayList(@PathParam("playlistName") String name, List<Song> playlist)
        throws UnknownPlaylistException;
}

Just like JAX-RS on the server side, this interface uses annotations like @Path, @Consumes, @GET and @PathParam. From this, we know that when a user invokes the getPlaylistNames method, the MicroProfile Rest Client implementation sends a GET request to the endpoint at <baseUrl>/playlist. The GET request accepts the application/json media type of a list of strings indicating the available names of playlists.

ResponseExceptionMappers

To see what songs are in a given playlist, invoke the getPlaylist method. Notice that this method throws an UnknownPlaylistException - this situation might be indicated on the remote service as returning an HTTP 404 response. In order to convert this response to a specific exception, we need a ResponseExceptionMapper like this:

@Provider
public class PlaylistResponseExceptionMapper implements
    ResponseExceptionMapper<BasePlaylistException> {

    @Override
    public boolean handles(int statusCode, MultivaluedMap<String, Object> headers) {
        return statusCode == 404  // Not Found
            || statusCode == 409; // Conflict
    }

    @Override
    public BasePlaylistException toThrowable(Response response) {
        switch(response.getStatus()) {
        case 404: return new UnknownPlaylistException();
        case 409: return new PlaylistAlreadyExistsException();
        }
        return null;
    }

}

This code assumes that both UnknownPlaylistException and PlaylistAlreadyExistsException are both sub-classes of BasePlaylistException. Notice that the toThrowable returns an instance of the throwable rather than throwing it.

Implementation

Now that we have the interface and response exception mapper written, we just need to build the implementation and then invoke it. There are two ways to build the implementation: using the RestClientBuilder API or using CDI and MicroProfile Config. We’ll start with the RestClientBuilder - it is a little more verbose but can come in handy in environments where CDI is not available, like testing.

RestClientBuilder

...
URL apiUrl = new URL("http://localhost:9080/onlineMusicService");
MusicPlaylistService playlistSvc =
    RestClientBuilder.newBuilder()
                     .baseUrl(apiUrl)
                     .register(PlaylistResponseExceptionMapper.class)
                     .build(MusicPlaylistService.class);

List<String> playlistNames = playlistSvc.getPlaylistNames();
for (String name : playlistNames) {
    List<Song> songs = playlistSvc.getPlaylist(name);
    if (hasSongBy(songs, "Band of Horses")) {
        coolPlaylists.add(name);
    }
}
...

First we create a new instance of the RestClientBuilder. We must specify the baseUrl value for the remote endpoint - this is required before building the client. Next, we register the response exception mapper. If we need to register other provider classes like MessageBodyReaders or MessageBodyWriters, filters, interceptors, etc., we would do that here with the register method. Then we build the client, passing in the interface class. After that we can invoke methods on the client like it was any other Java object.

CDI and MicroProfile Config integration

It is also possible to let CDI instantiate the client. For all of the code we have covered so far, the user only needs to install the mpRestClient-1.0 feature in the Liberty server.xml. In order for this CDI approach to operate, we need to add two more features: cdi-1.2 (or 2.0) and mpConfig-1.1 (or 1.2). First, we need to update the MusicPlaylistService with some new annotations:

@Path("/playlist")
@Consumes("application/json")
@Dependent
@RegisterRestClient
@RegisterProvider(PlaylistResponseExceptionMapper.class)
public interface MusicPlaylistService {
    ...

The @Dependent and @RegisterRestClient annotations declare that this interface is to be managed by CDI. The @RegisterProvider annotation tells the MicroProfile Rest Client implementation code to register the specified provider class. This annotation can be repeated for as many providers as necessary. Now we can inject the client in another managed object like this:

 @WebServlet(urlPatterns = "/PlaylistServlet")
public class PlaylistServlet extends HttpServlet {

    @Inject
    @RestClient
    private MusicPlaylistService playlistService;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        List<String> names = playlistService.getPlaylistNames();
        ...
    }

The @Inject combined with the @RestClient decorator tells CDI that we want to inject an instance of the MusicPlaylistService interface. There is still one more step…​ we need to tell the MicroProfile Rest Client implementation the baseUrl value for the remote endpoint. For that, we use MicroProfile Config. The config property to use is <fullyQualifiedInterfaceName>/mp-rest/url. So you could specify this as a system property in the jvm.options file like this:

-Dcom.mypkg.MusicPlaylistService/mp-rest/url=http://localhost:9080/onlineMusicService

CDI injection makes things a lot simpler when it comes to bootstrapping the client and, with MicroProfile Config, it is possible to use different URLs for different environments; for example, use one URL for test and another URL for production, without needing to change code.

Additional Information

For more information on the MicroProfile Rest Client, see the MicroProfile Rest Client 1.0 spec

Get involved in the MicroProfile community at: https://microprofile.io

Share this post: