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).
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.1.11.fuse-720057-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.1.11.fuse-720057-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:
- Bootstrap the client.
- Configure the target.
- Build and make the invocation.
- 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 formapplication/*+xml
). javax.xml.bind.JAXBElement
and application-supplied JAXB classes-
XML types (
text/xml
,application/xml
, and media types of the formapplication/*+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(); } }); ...