New Feature in Payara Platform 5.182: MicroProfile REST Client
Originally published on 16 Aug 2018
Last updated on 17 Jul 2019
The 5.182 release of Payara Server & Payara Micro (Payara Platform) brings in MicroProfile 1.3. This introduces a couple of updates to some existing specifications, and three new ones: OpenTracing, OpenAPI, and Type-safe REST Client. In this blog, I’ll be covering our implementation of the Type-safe REST Client.
Want to know more about MicroProfile in Payara Server and Payara Micro? Check out these articles on our blog:
- New Feature in Payara Server & Payara Micro 5.182: MicroProfile OpenTracing
- MicroProfile OpenAPI in the Payara Platform
- Building Your Next Microservice With Eclipse MicroProfile
- MicroProfile Metrics in Payara Micro
MicroProfile REST Client API provides a type-safe way to call remote REST services via a plain Java interface. It extends JAX-RS API by reusing the same annotations that are available to map incoming REST requests to a call of a Java method. But the annotations are applied on a client interface and are interpreted in an opposite direction to allow mapping of Java method call to a REST request.
Since JAX-RS provides only a programmatic client API, MicroProfile is a nice addition and allows separating business logic and a configuration of a REST call via an annotated Java interface.
You just need to create an interface with a method for each combination of paths, path parameters, request parameters and HTTP method types you need. Then you can just inject the interface:
@Inject
@RestClient
private HelloService helloService
And then simply call methods on the injected instance and a REST call will be issued behind the scenes:
helloService.hello("World");
Get Started: Add MicroProfile REST Client API
Before using the REST Client API, we need to add its API JAR into our application. With Maven, it's as easy as adding the following dependencies into the pom.xml:
<dependency> <groupId>org.eclipse.microprofile.rest.client</groupId> <artifactId>microprofile-rest-client-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.0.1</version> <scope>provided</scope> </dependency>
If you're not using Maven, you need to download the dependencies from Maven and add them to your project as provided dependencies (so that they're not added to the built WAR file).
Step 1: Declare a REST Client Interface
The first is very simple for a Java programmer: create a plain Java interface with methods that represent future REST calls. For example, let's create a HelloService
interface with a single method:
public interface HelloService {
String hello(String name);
}
We want to inject this interface using,@Inject @RestClient
so we'll mark the interface with the @RegisterRestClient
annotation provided by the MicroProfile REST Client API so that the container discovers it and enables for injection:
@RegisterRestClient
public interface HelloService
Note that injected instances will have @Dependent
scope by default, which means a new instance will be created for each injection. If you need to optimize this with a different scope, you can add a CDI scope annotation on the interface.
Now we need to declare the root URL which is common for all methods on the interface. This URL will be appended to a base URL which is configured separately because it depends on the location of the remote REST service. We use the @
javax.ws.rs.Path
annotation on the interface to define the root URL of "/api/hello":
@Path(
"/api/hello")
public interface HelloService
We can also define the default mime types produced in a request by the methods and accepted in a response using existing JAX-RS annotations @Consumes
and @Produces
(these can be overridden by annotations on individual methods):
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.TEXT_PLAIN)
public interface HelloService
We can also define additional JAX-RS providers using the @RegisterProvider annotation from the MicroProfile REST Client API:
@RegisterProvider(MyClientRequestFilter.class)
public interface HelloService
Step 2: Add Method Arguments
Very often we need some arguments to REST requests. Arguments can be either additional path elements in the URL, query parameters or message body for some HTTP methods. Even here we can reuse the same mapping configuration as in JAX-RS endpoint methods to map a Java method to an HTTP request and response. Let's see how.
Each individual method can be bound to a subpath of the path defined for the whole interface. If a @Path
annotation is applied to a method, its value is appended to the path. We can use path parameters to define URL segments, which can be mapped to method arguments using @javax.ws.rs.PathParam
. For example, we can define a "name" path parameter and map it to an argument like this:
@Path("{name}")
String hello(@PathParam("name") String name);
With this code, a call of hello("Payara")
will be transformed to an URL like http://localhost/api/hello/Payara.
Additional request parameters can be added with arguments annotated with @javax.ws.rs.QueryParam
that defines their name in the query. For example, we can add a "verbose" query parameter like this:
@Path("{name}")
String hello(@PathParam("name") String name, @QueryParam("verbose") boolean verbose);
And a call of hello("Payara", true)
will be transformed to an URL like http://localhost/api/hello/Payara?verbose=true.
For methods that accept a message body (such as POST and PUT), we can send the body as an additional argument. No annotation is needed for this, for example:
@POST
String saveGreeting(Greeting greeting);
Step 3: Use the REST Client Interface
An interface annotated with @RegisterRestClient
can be injected in any CDI bean. We just need to mark an injection point with the @RestClient
qualifier annotation:
@Inject
@RestClient
private HelloService helloService
With an acquired instance, we can now call any method on the interface to invoke a REST call. However, before we can do that, we need to specify one mandatory configuration option and that's the base URL of a remote service. All the options, including the base URL, can be specified using MicroProfile Config API, which is also included in a full MicroProfile set of APIs and is available in Payara Platform as well. To have a reasonable default value, we'll create a configuration file microprofile-config.properties
in a META-INF
folder on the classpath and add a default value for the base URL for our HelloService. This file will contain a property composed of the full name of the interface (including its package), "mp-rest" and "url", all separated by "/". For example, a configuration of the base url for our HelloService will look like this:
fish.payara.restclient.HelloService/mp-rest/url=http://localhost:8080/rest-client
The URL also includes the context path of the remote service and anything that should be prepended before the path specified in the @Path
annotation. In result, a URL of the REST service invoked from HelloService
would be
http://localhost:8080/rest-client/api/hello
.
All the other properties are either not needed or have reasonable default values. If you need, you can configure them using MicroProfile Config as well. Instead of the "url" property in the configuration key, you can use one of the following:
uri
(you can use this property instead ofurl
to set the base URL)- scope (set the CDI scope of the injected client proxies)
- providers (an alternative to using the
@RegisterProvider
annotation) - priority (will override the priority of a specific provider)
For more details about the properties, consult the section "Support for MicroProfile Config" of the MicroProfile REST Client specification document.
If we need more control over the creation and configuration of a REST client proxy, we can also use the programmatic approach to creating it. in order to do that, we'll use a builder retrieved by RestClientBuilder.newBuilder()
. This builder implements the same javax.ws.rs.core.Configurable
interface that is implemented by JAX-RS ClientBuilder
. We will, therefore, reuse the JAX-RS ClientBuilder API to configure the created MicroProfile REST Client proxy:
HelloService helloService = RestClientBuilder.newBuilder()
.baseUri(new URI("http://localhost:8080/rest-client"))
.build(HelloService.class);
Whatever way we retrieve a HelloService proxy, we can then call any method on the interface, like this:
String greeting = remoteApi.hello("World");
Behind the scenes, the Java method call is transformed to an HTTP call to a remote REST service and a subsequent response is transformed into a value returned from the method.
Call a REST Service Asynchronously
MicroProfile REST Client also supports asynchronous invocation of REST services since version 1.1, which is part of MicroProfile 1.4. We can use this API in the Payara Platform since version 5.182, as an addition to MicroProfile 1.3 which only includes support for synchronous REST clients. Full support of MicroProfile 1.4 and 2.0 features is planned for a future version of Payara Platform.
Similar to resource methods in the latest JAX-RS 2.1 version, methods in a MicroProfile REST client interface can also return CompletionStage
instead of a direct value. This allows delivering the response when it's ready even after a REST client method returns.
When a method that returns CompletionStage
is called, it will return as soon as possible, without waiting for the REST call to complete. The returned stage is completed (either with a result or exceptionally) when the REST call is completed or timed out. An important aspect of returning CompletionStage
is that the calling thread isn't blocked and can continue further processing. The REST call happens in the background, in a separate thread.
For example, we can turn our synchronous method hello
, which returns a String
, into an asynchronous one like this:
@Path("{name}")
@GET
CompletionStage<String> helloAsync(@PathParam("name") String name);
The asynchronous method can be called in the same way as the synchronous method. However, the method will return as soon s possible, without waiting for a response. The code to execute after the REST call returns a response should be chained using the returned CompletionStage
. For example, this code will transform a String response to upper case:
helloService.helloAsync("World (Async)")
.thenApply(String::toUpperCase);
The code chained to the CompletionStage
will be executed in a separate thread managed by the container. If you want to use a custom thread executor, you need to use the programmatic API. While it's not possible to define an executor for injected proxies, it's possible to specify a custom executor using the programmatic API like this:
HelloService remoteApi = RestClientBuilder.newBuilder()
.baseUri(new URI("http://localhost:8080/rest-client"))
.executorService(customExecutor)
.build(HelloService.class);
NOTE: You should always use a managed executor provided by the container. In Java EE containers, a managed executor can be retrieved by injecting a ManagedExecutorService or created with an injected ManagedThreadFactory
.
Further Reading
You can find an example Microprofile REST Client application with a full source code in Payara Examples repository.
The MicroProfile REST Client specification and API documentation provide more details.
If you're not familiar with the JAX-RS API, you can find out more about it at the JAX-RS 2.1 (JSR 370) specification page. If you already know JAX-RS API, MicroProfile REST Client will make it easier and more natural for you to invoke REST services without looking too much into the documentation. If you want to keep playing more with MicroProfile REST Client, I’d recommend looking at the MicroProfile REST Client repository to see how things are changing in MicroProfile REST Client 1.2 and beyond. And as always, feel free to raise enhancement requests or issues on our GitHub Issues page or forum.
Related Posts
Getting Started with Observability in Jakarta EE Applications: Why Observability Matters
Published on 22 Nov 2024
by Luqman Saeed
0 Comments
A Preview of Jakarta REST (JAX-RS) 4.0 in Jakarta EE 11
Published on 13 Nov 2024
by Luqman Saeed
0 Comments
The latest version of Jakarta REST (formerly Java API for RESTful Web Services, aka JAX-RS), Jakarta REST 4.0, will bring some notable improvements and changes as part of Jakarta EE 11. This release focuses on modernizing the specification by ...