Getting started with JAX-RS Client API

This article is a walk through JAX-RS Client API for testing REST Endpoint using WildFly / JBoss EAP 7 as REST provider.

JAX-RS includes a fluent, request building API to connect to your REST Services. WildFly application server uses RESTEasy as JAX-RS implementation therefore in this tutorial we will learn how to code a JAX-RS Client from a public available REST Service.

The REST Endpoint

For the purpose of this example, we will use a public REST service which is available at: https://jsonplaceholder.typicode.com/

You will find there some demo Endpoints you can use to test your REST Clients. For example, you can use the “/post” Path to check a list of fake Posts:

GET 	/posts
GET 	/posts/1
GET 	/posts/1/comments
GET 	/comments?postId=1
POST 	/posts
PUT 	/posts/1
PATCH 	/posts/1
DELETE /posts/1

Coding the REST Client

Too code JAX-RS Client, we will use the javax.ws.rs.client.Client interface which is in charge to manage and configure HTTP connections towards REST services. Behind the scenes, this interface creates a javax.ws.rs.client.WebTarget object, which represents a specific URI. The WebTarget object is hidden by the fluent API used in this example.

We then build the client request by invoking the request method of the WebTarget class and chaining the request with the get method that obviously issues an HTTP GET. The Java type contained in the response Entity is specified as the parameter to the invoked method (In this example a String). Creating and disposing a new Client is a costly task in terms of input/output. Mind to reuse it across your invocations.

One advantage of using JAX-RS Client technology is that you stream Java objects in and out of your service. Let’s see it with a practical example:

@Path("/posts")
public class DemoRESTClient {

    String remoteHost = "https://jsonplaceholder.typicode.com";
    private Client client = ClientBuilder.newClient();

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Data> getData() {

        List<Data> result = client.target("https://jsonplaceholder.typicode.com").path("/posts")
                .request(MediaType.APPLICATION_JSON).get(new GenericType<List<Data>>() {
                });

        client.close();
        return result;
    }
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Data addData(Data data) {

        Response response = client.target("https://jsonplaceholder.typicode.com").path("/posts")
                .request(MediaType.APPLICATION_JSON).post(Entity.json(data));
        Data dataReturn = response.readEntity(Data.class);

        client.close();
        return dataReturn;

    }
}

The above Endpoint, works as a REST Client for the jsonplaceholder.typicode.com. More in detail:

  • The getData method fetches the list of Posts, which are in JSON format, and transform them in a List of Data Objects. To do that, we infer the GenericType which we need for JSON to Object mapping.
  • The addData method executes an HTTP POST with an Entity (a Data object) as JSON.

Performance Tip

Don’t forget to close() your Client objects. Client objects pool connections for performance reasons. If you do not close them, you will get the following WARN message:

WARN [org.jboss.resteasy.client.jaxrs.i18n] (Finalizer) RESTEASY004687: Closing a class org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine instance for you. Please close clients yourself

Testing the Endpoint

The source code for this example includes all the Classes that you need to build the example (the Data POJO and the JAX-RS Activator). You can deploy the endpoint on WildFly using its Maven plugin:

$ mvn install wildfly:deploy

Then, head to the browser and test the GET Endpoint:

We can test also the POST Endpoint using Postman or just the Command Line:

curl -s -X POST http://localhost:8080/ee-rest-client/rest/posts -H  'Content-Type: application/json' -d  '{"userId":"1","id":"2","title":"headline","body":"hello"}'| jq
{
  "body": "hello",
  "id": 101,
  "title": "headline",
  "userId": 1
}

Using Parameters in your Client Request

So far so good. Let’s see some more advanced examples. The WebTarget interface includes additional methods to extend the URI you originally constructed it with. For example, we test the “GET /posts/1” endpoint, by constructing the path using a Template expression:

Data result = client.target(BASE_URL).path("/posts/{id}")
        .resolveTemplate("id", "1")
        .request(MediaType.APPLICATION_JSON)				
        .get(Data.class);

That will return the Post with id “1”:

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

In the next example, we will show how we can bind a QueyrParam to test the GET /comments?id Endpoint:

@GET
@Path("/comments")
@Produces(MediaType.APPLICATION_JSON)
public List<HashMap> getMap(@QueryParam("id") int id) {

    List<HashMap> result = client.target(BASE_URL).path("/comments").queryParam("id", id).request(MediaType.APPLICATION_JSON)
            .get(new GenericType<List<HashMap>>() {
            });

    client.close();
    return result;
}

The above snippets also shows how you can collect the List of comments in a List of HashMap objects, mapping all key/values. This is an alternative approach, if you don’t want to use DTO Objects but rather a generic Collection.

Running Async Requests

The following is slightly more complex example which performs the Client request asynchronously and uses an InvocationCallback to be notified when the async response has completed. To make an async call, we chain the async() method on the request and we include the InvocationCallback in the HTTP Request:

InvocationCallback<List<Data>> invocationCallback = new InvocationCallback<List<Data>>() {

        @Override
        public void completed(List<Data> list) {
        	assertTrue(list.size() > 0);
        }

        @Override
        public void failed(Throwable throwable) {
            // It should fail
            Assert.fail(throwable.getMessage());

        }
    };
    
client.target(BASE_URL).path("/posts").request(MediaType.APPLICATION_JSON)
            .async().get(invocationCallback);

Filtering the Client Request

Filters can be applied as well on the client side in order to perform some custom actions with the client request or the response received from the target resource.

There can be two types of Client filters that can be registered:

  • Client Request Filters: are executed before your HTTP request is sent to the server.
  • Client Response Filters: are executed after a response is received from the server, but before the response body is unmarshalled.

In the following example, we will show how to implement a Client Request filter to modify the HTTP Request being sent to the REST Service. To do that, we will create a class – DataRequestFilter – which implements javax.ws.rs.client.ClientRequestFilter:

public class DataRequestFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        String method = requestContext.getMethod();
        if ("POST".equals(method) && requestContext.hasEntity()) {
            Data mydata = (Data) requestContext.getEntity();
            mydata.setBody("Default body");
            requestContext.setEntity(mydata);
        }

    }

}

In the above filter, we are overriding the Body for the Data class with a default value. To use the Filter in the JAX-RS Client, all we need to do is registering it on the javax.ws.rs.client.Client inteface, as follows:

Data data = new Data(1, 2, "title", null);

Data dataReturn =
            client.register(DataRequestFilter.class).target(BASE_URL).path("/posts").request()
                    .post(Entity.entity(data, MediaType.APPLICATION_JSON), Data.class);

Conclusion

This article was a full walk through JAX-RS Client API for REST Services. The source code for this article is available here: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/ee-rest-client