https://github.com/openliberty/guide-microprofile-rest-client
A guide on how to use MicroProfile Rest Client to invoke RESTful microservices over HTTP in a type-safe way: https://openliberty.io/guides/microprofile-rest-client.html
https://github.com/openliberty/guide-microprofile-rest-client
Last synced: about 1 year ago
JSON representation
A guide on how to use MicroProfile Rest Client to invoke RESTful microservices over HTTP in a type-safe way: https://openliberty.io/guides/microprofile-rest-client.html
- Host: GitHub
- URL: https://github.com/openliberty/guide-microprofile-rest-client
- Owner: OpenLiberty
- License: other
- Created: 2018-04-02T14:50:20.000Z (about 8 years ago)
- Default Branch: prod
- Last Pushed: 2024-04-12T17:49:46.000Z (about 2 years ago)
- Last Synced: 2024-04-13T18:30:07.099Z (about 2 years ago)
- Language: Java
- Homepage:
- Size: 411 KB
- Stars: 11
- Watchers: 6
- Forks: 13
- Open Issues: 5
-
Metadata Files:
- Readme: README.adoc
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
// Copyright (c) 2018, 2025 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
:projectid: microprofile-rest-client
:page-layout: guide-multipane
:page-duration: 20 minutes
:page-releasedate: 2018-05-14
:page-guide-category: microprofile
:page-essential: true
:page-essential-order: 3
:page-description: Learn how to use MicroProfile Rest Client to invoke RESTful services over HTTP in a type-safe way.
:guide-author: Open Liberty
:page-tags: ['microprofile']
:page-permalink: /guides/{projectid}
:page-related-guides: ['rest-intro', 'cdi-intro', 'microprofile-config', 'microprofile-rest-client-async']
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
:page-seo-title: Consuming RESTful Java microservices with template interfaces using Eclipse MicroProfile Rest Client
:page-seo-description: A getting started tutorial and an example on how to consume RESTful Java microservices using template interfaces and Context and Dependency Injection (CDI) or a builder with MicroProfile Rest Client.
:source-highlighter: prettify
= Consuming RESTful services with template interfaces
[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].
Learn how to use MicroProfile Rest Client to invoke RESTful microservices over HTTP in a type-safe way.
// =================================================================================================
// What you'll learn
// =================================================================================================
== What you'll learn
You will learn how to build a MicroProfile Rest Client to access remote RESTful services. You will create a template interface that maps to the remote service that you want to call. MicroProfile Rest Client automatically generates a client instance based on what is defined and annotated in the template interface. Thus, you don't have to worry about all of the boilerplate code, such as setting up a client class, connecting to the remote server, or invoking the correct URI with the correct parameters.
The application that you will be working with is an `inventory` service, which fetches and stores the system property information for different hosts. Whenever a request is made to retrieve the system properties of a particular host, the `inventory` service will create a client to invoke the `system` service on that host. The `system` service simulates a remote service in the application.
You will instantiate the client and use it in the `inventory` service. You can choose from two different approaches, https://openliberty.io/docs/latest/cdi-beans.html[Context and Dependency Injection (CDI)^] with the help of MicroProfile Config or the https://openliberty.io/blog/2018/01/31/mpRestClient.html[RestClientBuilder^] method. In this guide, you will explore both methods to handle scenarios for providing a valid base URL.
* When the base URL of the remote service is static and known, define the default base URL in the configuration file. Inject the client with a CDI method.
* When the base URL is not yet known and needs to be determined during the run time, set the base URL as a variable. Build the client with the more verbose `RestClientBuilder` method.
// =================================================================================================
// Getting started
// =================================================================================================
[role=command]
include::{common-includes}/gitclone.adoc[]
include::{common-includes}/twyb-intro.adoc[]
The `system` microservice simulates a service that returns the system property information for the host. The `system` service is accessible at the http://localhost:9080/system/properties[http://localhost:9080/system/properties^] URL. In this case, `localhost` is the host name.
The `inventory` microservice makes a request to the `system` microservice and stores the system property information. To fetch and store your system information, visit the http://localhost:9080/inventory/systems/localhost[http://localhost:9080/inventory/systems/localhost^] URL.
// static guide instructions:
ifndef::cloud-hosted[]
You can also use the `\http://localhost:9080/inventory/systems/{your-hostname}` URL. In Windows, MacOS, and Linux, get your fully qualified domain name (FQDN) by entering `hostname` into your command-line. Visit the URL by replacing `{your-hostname}` with your FQDN.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
You can also use the ***http://localhost:9080/inventory/systems/{your-hostname}*** URL. In Windows, MacOS, and Linux, get your fully qualified domain name (FQDN) by entering **hostname** into your command-line. Visit the URL by replacing ***{your-hostname}*** with your FQDN.
endif::[]
[role=command]
include::{common-includes}/twyb-end.adoc[]
// =================================================================================================
// Writing the RESTful client interface
// =================================================================================================
== Writing the RESTful client interface
Now, navigate to the `start` directory to begin.
ifdef::cloud-hosted[]
```bash
cd /home/project/guide-microprofile-rest-client/start
```
endif::[]
[role=command]
include::{common-includes}/devmode-lmp33-start.adoc[]
The MicroProfile Rest Client API is included in the MicroProfile dependency specified by your [hotspot file=0]`pom.xml` file. Look for the dependency with the [hotspot=microprofile file=0]`microprofile` artifact ID.
pom.xml
[source,xml,linenums,role="code_column"]
----
include::finish/pom.xml[]
----
This dependency provides a library that is required to implement the MicroProfile Rest Client interface.
The [hotspot=mpRestClient file=1]`mpRestClient` feature is also enabled in the [hotspot file=1]`src/main/liberty/config/server.xml` file. This feature enables your Open Liberty to use MicroProfile Rest Client to invoke RESTful microservices.
server.xml
[source,xml,linenums,role="code_column"]
----
include::finish/src/main/liberty/config/server.xml[]
----
The code for the `system` service in the `src/main/java/io/openliberty/guides/system` directory is provided for you. It simulates a remote RESTful service that the `inventory` service invokes.
Create a RESTful client interface for the `system` service. Write a template interface that maps the API of the remote `system` service. The template interface describes the remote service that you want to access. The interface defines the resource to access as a method by mapping its annotations, return type, list of arguments, and exception declarations.
[role="code_command hotspot file=2", subs="quotes"]
----
#Create the `SystemClient` class.#
`src/main/java/io/openliberty/guides/inventory/client/SystemClient.java`
----
SystemClient.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java[]
----
The MicroProfile Rest Client feature automatically builds and generates a client implementation based on what is defined in the [hotspot=SystemClient file=2]`SystemClient` interface. There is no need to set up the client and connect with the remote service.
Notice the [hotspot=SystemClient file=2]`SystemClient` interface inherits the [hotspot=AutoCloseable file=2]`AutoCloseable` interface. This allows the user to explicitly close the client instance by invoking the `close()` method or to implicitly close the client instance using a try-with-resources block. When the client instance is closed, all underlying resources associated with the client instance are cleaned up. Refer to the https://github.com/eclipse/microprofile-rest-client/releases[MicroProfile Rest Client specification^] for more details.
When the [hotspot=getProperties file=2]`getProperties()` method is invoked, the [hotspot=SystemClient file=2]`SystemClient` instance sends a GET request to the `/properties` endpoint, where `` is the default base URL of the `system` service. You will see how to configure the base URL in the next section.
The [hotspot=Produces file=2]`@Produces` annotation specifies the media (MIME) type of the expected response. The default value is `MediaType.APPLICATION_JSON`.
The [hotspot=RegisterProvider file=2]`@RegisterProvider` annotation tells the framework to register the provider classes to be used when the framework invokes the interface. You can add as many providers as necessary. In the [hotspot=SystemClient file=2]`SystemClient` interface, add a response exception mapper as a provider to map the `404` response code with the [hotspot=getProperties file=2]`UnknownUriException` exception.
// =================================================================================================
// Handling exceptions through ResponseExceptionMappers
// =================================================================================================
=== Handling exceptions through ResponseExceptionMappers
Error handling is an important step to ensure that the application can fail safely. If there is an error response such as `404 NOT FOUND` when invoking the remote service, you need to handle it. First, define an exception, and map the exception with the error response code. Then, register the exception mapper in the client interface.
Look at the client interface again, the [hotspot=RegisterProvider file=0]`@RegisterProvider` annotation registers the `UnknownUriExceptionMapper` response exception mapper. An exception mapper maps various response codes from the remote service to throwable exceptions.
SystemClient.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java[]
----
Implement the actual exception class and the mapper class to see how this mechanism works.
[role="code_command hotspot file=1", subs="quotes"]
----
#Create the `UnknownUriException` class.#
`src/main/java/io/openliberty/guides/inventory/client/UnknownUriException.java`
----
UnknownUriException.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/client/UnknownUriException.java[]
----
Now, link the [hotspot=exception file=1]`UnknownUriException` class with the corresponding response code through a `ResponseExceptionMapper` mapper class.
[role="code_command hotspot file=2", subs="quotes"]
----
#Create the `UnknownUriExceptionMapper` class.#
`src/main/java/io/openliberty/guides/inventory/client/UnknownUriExceptionMapper.java`
----
UnknownUriExceptionMapper.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/client/UnknownUriExceptionMapper.java[]
----
The [hotspot=handles file=2]`handles()` method inspects the HTTP response code to determine whether an exception is thrown for the specific response, and the [hotspot=toThrowable file=2]`toThrowable()` method returns the mapped exception.
// =================================================================================================
// Injecting the client with dependency injection
// =================================================================================================
== Injecting the client with dependency injection
Now, instantiate the [hotspot=SystemClient file=1]`SystemClient` interface and use it in the `inventory` service. If you want to connect only with the default host name, you can easily instantiate the [hotspot=SystemClient file=1]`SystemClient` with CDI annotations. CDI injection simplifies the process of bootstrapping the client.
First, you need to define the base URL of the [hotspot=SystemClient file=1]`SystemClient` instance. Configure the default base URL with the MicroProfile Config feature. This feature is enabled for you in the [hotspot=mpConfig file=3]`server.xml` file.
[role="code_command hotspot file=0", subs="quotes"]
----
#Create the configuration file.#
`src/main/resources/META-INF/microprofile-config.properties`
----
microprofile-config.properties
[source, properties, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----
The [hotspot=baseUri file=0]`mp-rest/uri` base URL config property is configured to the default [hotspot=baseUri file=0]`\http://localhost:9080/system` URL.
This configuration is automatically picked up by the MicroProfile Config API.
Look at the annotations in the [hotspot=SystemClient file=1]`SystemClient` interface again.
SystemClient.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java[]
----
The [hotspot=RegisterRestClient file=1]`@RegisterRestClient` annotation registers the interface as a RESTful client. The runtime creates a CDI managed bean for every interface that is annotated with the `@RegisterRestClient` annotation.
The [hotspot=RegisterRestClient file=1]`configKey` value in the [hotspot=RegisterRestClient file=1]`@RegisterRestClient` annotation replaces the fully-qualified classname of the properties in the [hotspot file=0]`microprofile-config.properties` configuration file. For example, the `/mp-rest/uri` property becomes `systemClient/mp-rest/uri`. The benefit of using Config Keys is when multiple client interfaces have the same `configKey` value, the interfaces can be configured with a single MP config property.
The [hotspot=RegisterRestClient file=1]`baseUri` value can also be set in the [hotspot=RegisterRestClient file=1]`@RegisterRestClient` annotation. However, this value will be overridden by the base URI property defined in the [hotspot file=0]`microprofile-config.properties` configuration file, which takes precedence. In a production environment, you can use the `baseUri` variable to specify a different URI for development and testing purposes.
The [hotspot=RegisterRestClient file=1]`@RegisterRestClient` annotation, which is a bean defining annotation implies that the interface is manageable through CDI. You must have this annotation in order to inject the client.
Inject the [hotspot=SystemClient file=1]`SystemClient` interface into the [hotspot file=2]`InventoryManager` class, which is another CDI managed bean.
[role="code_command hotspot file=2", subs="quotes"]
----
#Replace the `InventoryManager` class.#
`src/main/java/io/openliberty/guides/inventory/InventoryManager.java`
----
InventoryManager.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryManager.java[]
----
[hotspot=Inject file=2]`@Inject` and [hotspot=RestClient file=2]`@RestClient` annotations inject an instance of the [hotspot=SystemClient file=2]`SystemClient` called `defaultRestClient` to the [hotspot file=2]`InventoryManager` class.
Because the [hotspot file=2]`InventoryManager` class is [hotspot=ApplicationScoped file=2]`@ApplicationScoped`, and the [hotspot=SystemClient file=1]`SystemClient` CDI bean maintains the same scope through the default dependent scope, the client is initialized once per application.
If the `hostname` parameter is `localhost`, the service runs the [hotspot=getPropertiesWithDefaultHostName file=2]`getPropertiesWithDefaultHostName()` helper function to fetch system properties. The helper function invokes the `system` service by calling the [hotspot=defaultRCGetProperties file=2]`defaultRestClient.getProperties()` method.
server.xml
[source,xml,linenums,role="code_column"]
----
include::finish/src/main/liberty/config/server.xml[]
----
// =================================================================================================
// Building the client with RestClientBuilder
// =================================================================================================
== Building the client with RestClientBuilder
The `inventory` service can also connect with a host other than the default `localhost` host, but you cannot configure a base URL that is not yet known. In this case, set the host name as a variable and build the client by using the `RestClientBuilder` method. You can customize the base URL from the host name attribute.
Look at the [hotspot=getPropertiesWithGivenHostName]`getPropertiesWithGivenHostName()` method in the [hotspot file=0]`src/main/java/io/openliberty/guides/inventory/InventoryManager.java` file.
InventoryManager.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryManager.java[]
----
The host name is provided as a parameter. This method first assembles the base URL that consists of the new host name. Then, the method instantiates a [hotspot=customRestClientBuilder]`RestClientBuilder` builder with the new URL, registers the response exception mapper, and builds the `SystemClient` instance.
Similarly, call the [hotspot=customRCGetProperties]`customRestClient.getProperties()` method to invoke the `system` service.
// =================================================================================================
// Building and running the application
// =================================================================================================
[role=command]
include::{common-includes}/devmode-build.adoc[]
When the Liberty instance is running, select either approach to fetch your system properties:
* Visit the http://localhost:9080/inventory/systems/localhost[http://localhost:9080/inventory/systems/localhost^] URL. The URL retrieves the system property information for the `localhost` host name by making a request to the `system` service at `\http://localhost:9080/system/properties`.
// static guide instructions:
ifndef::cloud-hosted[]
* Get your FQDN first. Then, visit the `\http://localhost:9080/inventory/systems/{your-hostname}` URL by replacing `{your-hostname}` with your FQDN, which retrieves your system properties by making a request to the `system` service at `\http://{your-hostname}:9080/system/properties`.
endif::[]
// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
Or, get your FQDN first. Then, visit the ***http://localhost:9080/inventory/systems/{your-hostname}*** URL by replacing ***{your-hostname}*** with your FQDN, which retrieves your system properties by making a request to the ***system*** service at ***http://{your-hostname}:9080/system/properties***.
endif::[]
// =================================================================================================
// Testing the application
// =================================================================================================
== Testing the application
[role="code_command hotspot", subs="quotes"]
----
#Create the `RestClientIT` class.#
`src/test/java/it/io/openliberty/guides/client/RestClientIT.java`
----
RestClientIT.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/it/io/openliberty/guides/client/RestClientIT.java[]
----
Each test case tests one of the methods for instantiating a RESTful client.
The [hotspot=testDefaultLocalhost file=0]`testDefaultLocalhost()` test fetches and compares system properties from the \http://localhost:9080/inventory/systems/localhost URL.
The [hotspot=testRestClientBuilder file=0]`testRestClientBuilder()` test gets your IP address. Then, use your IP address as the host name to fetch your system properties and compare them.
In addition, a few endpoint tests are provided for you to test the basic functionality of the `inventory` and `system` services. If a test failure occurs, you might have introduced a bug into the code.
[role=command]
include::{common-includes}/devmode-test.adoc[]
[source, role="no_copy"]
----
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.system.SystemEndpointIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.377 sec - in it.io.openliberty.guides.system.SystemEndpointIT
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Interceptor for {http://client.inventory.guides.openliberty.io/}SystemClient has thrown exception, unwinding now
Could not send Message.
[err] The specified host is unknown.
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.379 sec - in it.io.openliberty.guides.inventory.InventoryEndpointIT
Running it.io.openliberty.guides.client.RestClientIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.121 sec - in it.io.openliberty.guides.client.RestClientIT
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
----
The warning and error messages are expected and result from a request to a bad or an unknown hostname. This request is made in the `testUnknownHost()` test from the `InventoryEndpointIT` integration test.
To see whether the tests detect a failure, change the base URL in the configuration file so that when the `inventory` service tries to access the invalid URL, an `UnknownUriException` is thrown. Rerun the tests to see a test failure occur.
[role=command]
include::{common-includes}/devmode-quit-ctrlc.adoc[]
== Great work! You're done!
You just invoked a remote service by using a template interface with MicroProfile Rest Client in Open Liberty.
MicroProfile Rest Client also provides a uniform way to configure SSL for the client. You can learn more in the https://openliberty.io/blog/2019/06/21/microprofile-rest-client-19006.html#ssl[Hostname verification with SSL on Open Liberty and MicroProfile Rest Client^] blog and the https://github.com/eclipse/microprofile-rest-client/releases[MicroProfile Rest Client specification^].
Feel free to try one of the related guides where you can learn more technologies and expand on what you built here.
include::{common-includes}/attribution.adoc[subs="attributes"]