Chapter 49. JAX-RS 2.0 Client API

Abstract

JAX-RS 2.0 defines a full-featured client API which can be used for making REST invocations or any HTTP client invocations. This includes a fluent API (to simplify building up requests), a framework for parsing messages (based on a type of plug-in known as an entity provider), and support for asynchronous invocations on the client side.

49.1. Introduction to the JAX-RS 2.0 Client API

Overview

JAX-RS 2.0 defines a fluent API for JAX-RS clients, which enables you to build up a HTTP request step-by-step and then invoke the request using the appropriate HTTP verb (GET, POST, PUT, or DELETE).

Note

It is also possible to define a JAX-RS client in Blueprint XML or Spring XML (using the jaxrs:client element). For details of this approach, see Section 18.2, “Configuring JAX-RS Client Endpoints”.

Dependencies

To use the JAX-RS 2.0 client API in your application, you must add the following Maven dependency to your project’s pom.xml file:

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-rs-client</artifactId>
  <version>3.2.7.fuse-770017-redhat-00001</version>
</dependency>

If you plan to use the asynchronous invocation feature (see Section 49.6, “Asynchronous Processing on the Client”), you also need the following Maven dependency:

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http-hc</artifactId>
  <version>3.2.7.fuse-770017-redhat-00001</version>
</dependency>

Client API package

The JAX-RS 2.0 client interfaces and classes are located in the following Java package:

javax.ws.rs.client

When developing JAX-RS 2.0 Java clients, you also typically need to access classes from the core package:

javax.ws.rs.core

Example of a simple client request

The following code fragment shows a simple example, where the JAX-RS 2.0 client API is used to make an invocation on the http://example.org/bookstore JAX-RS service, invoking with the GET HTTP method:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
...
Client client = ClientBuilder.newClient();
Response res = client.target("http://example.org/bookstore/books/123")
    .request("application/xml").get();

Fluent API

The JAX-RS 2.0 client API is designed as a fluent API (sometimes called a Domain Specific Language). In the fluent API, a chain of Java methods is invoked in a single statement, in such a way that the Java methods look like the commands from a simple language. In JAX-RS 2.0, the fluent API is used to build and invoke a REST request.

Steps to make a REST invocation

Using the JAX-RS 2.0 client API, a client invocation is built and invoked in a series of steps, as follows:

  1. Bootstrap the client.
  2. Configure the target.
  3. Build and make the invocation.
  4. Parse the response.

Bootstrap the client

The first step is to bootstrap the client, by creating a javax.ws.rs.client.Client object. This Client instance is a relatively heavyweight object, which represents the stack of technologies required to support a JAX-RS client (possibly including, interceptors and additional CXF features). Ideally, you should re-use client objects when you can, instead of creating new ones.

To create a new Client object, invoke a static method on the ClientBuilder class, as follows:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
...
Client client = ClientBuilder.newClient();
...

Configure the target

By configuring the target, you effectively define the URI that will be used for the REST invocation. The following example shows how you can define a base URI, base, and then add additional path segments to the base URI, using the path(String) method:

// Java
import javax.ws.rs.client.WebTarget;
...
WebTarget base = client.target("http://example.org/bookstore/");
WebTarget books = base.path("books").path("{id}");
...

Build and make the invocation

This is really two steps rolled up into one: firstly, you build up the HTTP request (including headers, accepted media types, and so on); and secondly, you invoke the relevant HTTP method (optionally providing a request message body, if one is required).

For example, to create and invoke a request that accepts the application/xml media type:

// Java
import javax.ws.rs.core.Response;
...
Response resp = books.resolveTemplate("id", "123").request("application/xml").get();

Parse the response

Finally, you need to parse the respose, resp, obtained in the previous step. Usually, the response is returned in the form of a javax.ws.rs.core.Response object, which encapsulates HTTP headers, along with other HTTP metadata, and the HTTP message body (if any).

If you want to access the returned HTTP message in String format, you can easily do so by invoking the readEntity method with a String.class argument, as follows:

// Java
...
String msg = resp.readEntity(String.class);

You can always access the message body of a response as a String, by specifying String.class as the argument to readEntity. For more general transformations or conversions of the message body, you can provide an entity provider to perform the conversion. For more details, see Section 49.4, “Parsing Requests and Responses”.

49.2. Building the Client Target

Overview

After creating the initial Client instance, the next step is to build up the request URI. The WebTarget builder class enables you to configure all aspects of the URI, including the URI path and query parameters.

WebTarget builder class

The javax.ws.rs.client.WebTarget builder class provides the part of the fluent API that enables you to build up the REST URI for the request.

Create the client target

To create a WebTarget instance, invoke one of the target methods on a javax.ws.rs.client.Client instance. For example:

// Java
import javax.ws.rs.client.WebTarget;
...
WebTarget base = client.target("http://example.org/bookstore/");

Base path and path segments

You can specify the complete path all in one go, using the target method; or you can specify a base path, and then add path segments piece by piece, using a combination of the target method and the path methods. The advantage of combining a base path with path segments is that you can easily re-use the base path WebTarget object for multiple invocations on slightly different targets. For example:

// Java
import javax.ws.rs.client.WebTarget;
...
WebTarget base = client.target("http://example.org/bookstore/");
WebTarget headers     = base.path("bookheaders");
// Now make some invocations on the 'headers' target...
...
WebTarget collections = base.path("collections");
// Now make some invocations on the 'collections' target...
...

URI template parameters

The syntax of the target path also supports URI template parameters. That is, a path segment can be initialized with a template parameter, {param}, which subsequently gets resolved to a specify value. For example:

// Java
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
...
WebTarget base = client.target("http://example.org/bookstore/");
WebTarget books = base.path("books").path("{id}");
...
Response resp = books.resolveTemplate("id", "123").request("application/xml").get();

Where the resolveTemplate method replaces the path segment, {id}, with the value 123.

Define query parameters

Query parameters can be appended to the URI path, where the beginning of the query parameters is marked by a single ? character. This mechanism enables you to set a series of name/value pairs, using the syntax: ?name1=value1&name2=value2&…​

A WebTarget instance enables you to define query parameters using the queryParam method, as follows:

// Java
WebTarget target = client.target("http://example.org/bookstore/")
                         .queryParam("userId","Agamemnon")
                         .queryParam("lang","gr");

Define matrix parameters

Matrix parameters are somewhat similar to query parameters, but are not as widely supported and use a different syntax. To define a matrix parameter on a WebTarget instance, invoke the matrixParam(String, Object) method.

49.3. Building the Client Invocation

Overview

After building the target URI, using the WebTarget builder class, the next step is to configure the other aspects of the request—such as HTTP headers, cookies, and so on—using the Invocation.Builder class. The final step in building the invocation is to invoke the appropriate HTTP verb (GET, POST, PUT, or DELETE) and provide a message body, if required.

Invocation.Builder class

The javax.ws.rs.client.Invocation.Builder builder class provides the part of the fluent API that enables you to build up the contents of the HTTP message and to invoke a HTTP method.

Create the invocation builder

To create an Invocation.Builder instance, invoke one of the request methods on a javax.ws.rs.client.WebTarget instance. For example:

// Java
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.client.Invocation.Builder;
...
WebTarget books = client.target("http://example.org/bookstore/books/123");
Invocation.Builder invbuilder = books.request();

Define HTTP headers

You can add a HTTP header to the request message using the header method, as follows:

Invocation.Builder invheader = invbuilder.header("From", "fionn@example.org");

Define cookies

You can add a cookie to the request message using the cookie method, as follows:

Invocation.Builder invcookie = invbuilder.cookie("myrestclient", "123xyz");

Define properties

You can set a property in the context of this request using the property method, as follows:

Invocation.Builder invproperty = invbuilder.property("Name", "Value");

Define accepted media types, languages, or encodings

You can define accepted media types, languages, or encodings, as follows:

Invocation.Builder invmedia = invbuilder.accept("application/xml")
                                        .acceptLanguage("en-US")
                                        .acceptEncoding("gzip");

Invoke HTTP method

The process of building a REST invocation is terminated by invoking a HTTP method, which performs the HTTP invocation. The following methods (inherited from the javax.ws.rs.client.SyncInvoker base class) can be invoked:

get
post
delete
put
head
trace
options

If the specific HTTP verb you want to invoke is not on this list, you can use the generic method method to invoke any HTTP method.

Typed responses

All of the HTTP invocation methods are provided with an untyped variant and a typed variant (which takes an extra argument). If you invoke a request using the default get() method (taking no arguments), a javax.ws.rs.core.Response object is returned from the invocation. For example:

Response res = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml").get();

It is also possible, however, to ask for the response to be returned as a specific type, using the get(Class<T>) method. For example, to invoke a request and ask for the response to be returned as a BookInfo object:

BookInfo res = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml").get(BookInfo.class);

In order for this to work, however, you must register a suitable entity provider with the Client instance, which is capable of mapping the response format, application/xml, to the requested type. For more details about entity providers, see Section 49.4, “Parsing Requests and Responses”.

Specifying the outgoing message in post or put

For HTTP methods that include a message body in the request (such as POST or PUT), you must specify the message body as the first argument of the method. The message body must be specified as a javax.ws.rs.client.Entity object, where the Entity encapsulates the message contents and its associated media type. For example, to invoke a POST method, where the message contents are provided as a String type:

import javax.ws.rs.client.Entity;
...
Response res = client.target("http://example.org/bookstore/registerbook")
                     .request("application/xml")
                     .put(Entity.entity("Red Hat Install Guide", "text/plain"));

If necessary, the Entity.entity() constructor method will automatically map the supplied message instance to the specified media type, using the registered entity providers. It is always possible to specify the message body as a simple String type.

Delayed invocation

Instead of invoking the HTTP request right away (for example, by invoking the get() method), you have the option of creating an javax.ws.rs.client.Invocation object, which can be invoked at a later time. The Invocation object encapsulates all of the details of the pending invocation, including the HTTP method.

The following methods can be used to build an Invocation object:

buildGet
buildPost
buildDelete
buildPut
build

For example, to create a GET Invocation object and invoke it at a later time, you can use code like the following:

import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Response;
...
Invocation getBookInfo = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml").buildGet();
...
// Later on, in some other part of the application:
Response = getBookInfo.invoke();

Asynchronous invocation

The JAX-RS 2.0 client API supports asynchronous invocations on the client side. To make an asynchronous invocation, simply invoke the async() method in the chain of methods following request(). For example:

Future<Response> res = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml")
                     .async()
                     .get();

When you make an asynchronous invocation, the returned value is a java.util.concurrent.Future object. For more details about asynchronous invocations, see Section 49.6, “Asynchronous Processing on the Client”.

49.4. Parsing Requests and Responses

Overview

An essential aspect of making HTTP invocations is that the client must be able to parse the outgoing request messages and the incoming responses. In JAX-RS 2.0, the key concept is the Entity class, which represents a raw message tagged with a media type. In order to parse the raw message, you can register multiple entity providers, which have the capability to convert media types to and from particular Java types.

In other words, in the context of JAX-RS 2.0, an Entity is the representation of a raw message and an entity provider is the plug-in that provides the capability to parse the raw message (based on the media type).

Entities

An Entity is a message body augmented by metadata (media type, language, and encoding). An Entity instance holds the message in a raw format and is associated with a specific media type. To convert the contents of an Entity object to a Java object you require an entity provider, which is capable of mapping the given media type to the required Java type.

Variants

A javax.ws.rs.core.Variant object encapsulates the metadata associated with an Entity, as follows:

  • Media type,
  • Language,
  • Encoding.

Effectively, you can think of an Entity as consisting of the HTTP message contents, augmented by Variant metadata.

Entity providers

An entity provider is a class that provides the capability of mapping between a media type and a Java type. Effectively, you can think of an entity provider as a class that provides the ability to parse messages of a particular media type (or possibly of multiple media types). There are two different varieties of entity provider:

MessageBodyReader
Provides the capability of mapping from media type(s) to a Java type.
MessageBodyWriter
Provides the capability of mapping from a Java type to a media type.

Standard entity providers

Entity providers for the following Java and media type combinations are provided as standard:

byte[]
All media types ( */* ).
java.lang.String
All media types ( */* ).
java.io.InputStream
All media types ( */* ).
java.io.Reader
All media types ( */* ).
java.io.File
All media types ( */* ).
javax.activation.DataSource
All media types ( */* ).
javax.xml.transform.Source
XML types (text/xml, application/xml, and media types of the form application/*+xml).
javax.xml.bind.JAXBElement and application-supplied JAXB classes
XML types (text/xml, application/xml, and media types of the form application/*+xml).
MultivaluedMap<String,String>
Form content (application/x-www-form-urlencoded).
StreamingOutput
All media types (*/*), MessageBodyWriter only.
java.lang.Boolean, java.lang.Character, java.lang.Number
Only for text/plain. Corresponding primitive types supported through boxing/unboxing conversion.

Response object

The default return type is the javax.ws.rs.core.Response type, which represents an untyped response. The Response object provides access to the complete HTTP response, including the message body, HTTP status, HTTP headers, media type, and so on.

Accessing the response status

You can access the response status, either through the getStatus method (which returns the HTTP status code):

int status = resp.getStatus();

Or though the getStatusInfo method, which also provides a description string:

String statusReason = resp.getStatusInfo().getReasonPhrase();

Accessing the returned headers

You can access the HTTP headers using any of the following methods:

MultivaluedMap<String,Object>
getHeaders()

MultivaluedMap<String,String>
getStringHeaders()

String
getHeaderString(String name)

For example, if you know that the Response has a Date header, you could access it as follows:

String dateAsString = resp.getHeaderString("Date");

Accessing the returned cookies

You can access any new cookies set on the Response using the getCookies method, as follows:

import javax.ws.rs.core.NewCookie;
...
java.util.Map<String,NewCookie> cookieMap = resp.getCookies();
java.util.Collection<NewCookie> cookieCollection = cookieMap.values();

Accessing the returned message content

You can access the returned message content by invoking one of the readEntity methods on the Response object. The readEntity method automatically invokes the available entity providers to convert the message to the requested type (specified as the first argument of readEntity). For example, to access the message content as a String type:

String messageBody = resp.readEntity(String.class);

Collection return value

If you need to access the returned message as a Java generic type—for example, as a List or Collection type—you can specify the request message type using the javax.ws.rs.core.GenericType<T> construction. For example:

import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.GenericType;
import java.util.List;
...
GenericType<List<String>> stringListType = new GenericType<List<String>>() {};

Client client = ClientBuilder.newClient();
List<String> bookNames = client.target("http://example.org/bookstore/booknames")
                     .request("text/plain")
                     .get(stringListType);

49.5. Configuring the Client Endpoint

Overview

It is possible to augment the functionality of the base javax.ws.rs.client.Client object by registering and configuring features and providers.

Example

The following example shows a client configured to have a logging feature, a custom entity provider, and to set the prettyLogging property to true:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import org.apache.cxf.feature.LoggingFeature;
...
Client client = ClientBuilder.newClient();
client.register(LoggingFeature.class)
      .register(MyCustomEntityProvider.class)
      .property("LoggingFeature.prettyLogging","true");

Configurable API for registering objects

The Client class supports the Configurable API for registering objects, which provides several variants of the register method. In most cases, you would register either a class or an object instance, as shown in the following examples:

client.register(LoggingFeature.class)
client.register(new LoggingFeature())

For more details about the register variants, see the reference documentation for Configurable.

What can you configure on the client?

You can configure the following aspects of a client endpoint:

  • Features
  • Providers
  • Properties
  • Filters
  • Interceptors

Features

A javax.ws.rs.core.Feature is effectively a plug-in that adds an extra feature or functionality to a JAX-RS client. Often, a feature installs one or more interceptors in order to provide the required functionality.

Providers

A provider is a particular kind of client plug-in that provides a mapping capability. The JAX-RS 2.0 specification defines the following kinds of provider:

Entity providers
An entity provider provides the capability of mapping between a specific media type a Java type. For more details, see Section 49.4, “Parsing Requests and Responses”.
Exception mapping providers
An exception mapping provider maps a checked runtime exception to an instance of Response.
Context providers
A context provider is used on the server side, to supply context to resource classes and other service providers.

Filters

A JAX-RS 2.0 filter is a plug-in that gives you access to the URI, headers, and miscellaneous context data at various points (extension points) of the message processing pipeline. For details, see Chapter 61, JAX-RS 2.0 Filters and Interceptors.

Interceptors

A JAX-RS 2.0 interceptor is a plug-in that gives you access to the message body of a request or response as it is being read or written. For details, see Chapter 61, JAX-RS 2.0 Filters and Interceptors.

Properties

By setting one or more properties on the client, you can customize the configuration of a registered feature or a registered provider.

Other configurable types

It is possible, not only to configure a javax.ws.rs.client.Client (and javax.ws.rs.client.ClientBuilder) object, but also a WebTarget object. When you change the configuration of a WebTarget object, the underlying client configuration is deep copied to give the new WebTarget configuration. Hence, it is possible to change the configuration of the WebTarget object without changing the configuration of the original Client object.

49.6. Asynchronous Processing on the Client

Overview

JAX-RS 2.0 supports asynchronous processing of invocations on the client side. Two different styles of asynchronous processing are supported: either using a java.util.concurrent.Future<V> return value; or by registering an invocation callback.

Asynchronous invocation with Future<V> return value

Using the Future<V> approach to asynchronous processing, you can invoke a client request asynchronously, as follows:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import java.util.concurrent.Future;
import javax.ws.rs.core.Response;
...
Client client = ClientBuilder.newClient();
Future<Response> futureResp = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml")
                     .async()
                     .get();
...
// At a later time, check (and wait) for the response:
Response resp = futureResp.get();

You can use a similar approach for typed responses. For example, to get a response of type, BookInfo:

Client client = ClientBuilder.newClient();
Future<BookInfo> futureResp = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml")
                     .async()
                     .get(BookInfo.class);
...
// At a later time, check (and wait) for the response:
BookInfo resp = futureResp.get();

Asynchronous invocation with invocation callback

Instead of accessing the return value using a Future<V> object, you can define an invocation callback (using javax.ws.rs.client.InvocationCallback<RESPONSE>), as follows:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import java.util.concurrent.Future;
import javax.ws.rs.core.Response;
import javax.ws.rs.client.InvocationCallback;
...
Client client = ClientBuilder.newClient();
Future<Response> futureResp = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml")
                     .async()
                     .get(
    new InvocationCallback<Response>() {
        @Override
        public void completed(final Response resp) {
            // Do something when invocation is complete
            ...
        }

        @Override
        public void failed(final Throwable throwable) {
            throwable.printStackTrace();
        }
    });
...

You can use a similar approach for typed responses:

// Java
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
import java.util.concurrent.Future;
import javax.ws.rs.core.Response;
import javax.ws.rs.client.InvocationCallback;
...
Client client = ClientBuilder.newClient();
Future<BookInfo> futureResp = client.target("http://example.org/bookstore/books/123")
                     .request("application/xml")
                     .async()
                     .get(
    new InvocationCallback<BookInfo>() {
        @Override
        public void completed(final BookInfo resp) {
            // Do something when invocation is complete
            ...
        }

        @Override
        public void failed(final Throwable throwable) {
            throwable.printStackTrace();
        }
    });
...