Consuming RESTful services with template interfaces

duration 20 minutes

Prerequisites:

Learn how to use MicroProfile Rest Client to invoke RESTful microservices over HTTP in a type-safe way.

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, Context and Dependency Injection (CDI) with the help of MicroProfile Config or the 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

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-rest-client.git
cd guide-microprofile-rest-client

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready:

The defaultServer server is ready to run a smarter planet.

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 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 URL.

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.

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Writing the RESTful client interface

Now, navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

The MicroProfile Rest Client API is included in the MicroProfile dependency specified by your pom.xml file. Look for the dependency with the microprofile artifact ID.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  3                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4    <modelVersion>4.0.0</modelVersion>
  5
  6    <groupId>io.openliberty.guides</groupId>
  7    <artifactId>guide-microprofile-rest-client</artifactId>
  8    <version>1.0-SNAPSHOT</version>
  9    <packaging>war</packaging>
 10
 11    <properties>
 12        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14        <maven.compiler.source>11</maven.compiler.source>
 15        <maven.compiler.target>11</maven.compiler.target>
 16        <!-- Liberty configuration -->
 17        <liberty.var.http.port>9080</liberty.var.http.port>
 18        <liberty.var.https.port>9443</liberty.var.https.port>
 19    </properties>
 20
 21    <dependencies>
 22        <!-- Provided dependencies -->
 23        <dependency>
 24            <groupId>jakarta.platform</groupId>
 25            <artifactId>jakarta.jakartaee-api</artifactId>
 26            <version>10.0.0</version>
 27            <scope>provided</scope>
 28        </dependency>
 29        <dependency>
 30            <groupId>org.eclipse.microprofile</groupId>
 31            <!-- tag::microprofile[] -->
 32            <artifactId>microprofile</artifactId>
 33            <!-- end::microprofile[] -->
 34            <version>6.1</version>
 35            <type>pom</type>
 36            <scope>provided</scope>
 37        </dependency>
 38        <!-- Java utility classes -->
 39        <dependency>
 40            <groupId>org.apache.commons</groupId>
 41            <artifactId>commons-lang3</artifactId>
 42            <version>3.14.0</version>
 43        </dependency>
 44        <!-- For tests-->
 45        <dependency>
 46            <groupId>org.junit.jupiter</groupId>
 47            <artifactId>junit-jupiter</artifactId>
 48            <version>5.10.2</version>
 49            <scope>test</scope>
 50        </dependency>
 51        <dependency>
 52            <groupId>org.jboss.resteasy</groupId>
 53            <artifactId>resteasy-client</artifactId>
 54            <version>6.2.7.Final</version>
 55            <scope>test</scope>
 56        </dependency>
 57        <dependency>
 58            <groupId>org.jboss.resteasy</groupId>
 59            <artifactId>resteasy-json-binding-provider</artifactId>
 60            <version>6.2.7.Final</version>
 61            <scope>test</scope>
 62        </dependency>
 63        <dependency>
 64            <groupId>org.glassfish</groupId>
 65            <artifactId>jakarta.json</artifactId>
 66            <version>2.0.1</version>
 67            <scope>test</scope>
 68        </dependency>
 69    </dependencies>
 70
 71    <build>
 72        <finalName>${project.artifactId}</finalName>
 73        <plugins>
 74            <plugin>
 75                <groupId>org.apache.maven.plugins</groupId>
 76                <artifactId>maven-war-plugin</artifactId>
 77                <version>3.4.0</version>
 78            </plugin>
 79            <!-- Plugin to run unit tests -->
 80            <plugin>
 81                <groupId>org.apache.maven.plugins</groupId>
 82                <artifactId>maven-surefire-plugin</artifactId>
 83                <version>3.2.5</version>
 84            </plugin>
 85            <!-- Enable liberty-maven plugin -->
 86            <plugin>
 87                <groupId>io.openliberty.tools</groupId>
 88                <artifactId>liberty-maven-plugin</artifactId>
 89                <version>3.10.2</version>
 90            </plugin>
 91            <!-- Plugin to run functional tests -->
 92            <plugin>
 93                <groupId>org.apache.maven.plugins</groupId>
 94                <artifactId>maven-failsafe-plugin</artifactId>
 95                <version>3.2.5</version>
 96                <configuration>
 97                    <systemPropertyVariables>
 98                        <http.port>${liberty.var.http.port}</http.port>
 99                    </systemPropertyVariables>
100                </configuration>
101            </plugin>
102        </plugins>
103    </build>
104</project>

This dependency provides a library that is required to implement the MicroProfile Rest Client interface.

The mpRestClient feature is also enabled in the src/main/liberty/config/server.xml file. This feature enables your Open Liberty to use MicroProfile Rest Client to invoke RESTful microservices.

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonp-2.1</feature>
 6    <feature>jsonb-3.0</feature>
 7    <feature>cdi-4.0</feature>
 8    <!-- tag::mpConfig[] -->
 9    <feature>mpConfig-3.1</feature>
10    <!-- end::mpConfig[] -->
11    <!-- tag::mpRestClient[] -->
12    <feature>mpRestClient-3.0</feature>
13    <!-- end::mpRestClient[] -->
14  </featureManager>
15
16  <variable name="http.port" defaultValue="9080"/>
17  <variable name="https.port" defaultValue="9443"/>
18  
19  <httpEndpoint host="*" httpPort="${http.port}" httpsPort="${https.port}"
20                id="defaultHttpEndpoint"/>
21
22  <webApplication location="guide-microprofile-rest-client.war" contextRoot="/"/>
23</server>

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.

Create the SystemClient class.
src/main/java/io/openliberty/guides/inventory/client/SystemClient.java

SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::client[]
13package io.openliberty.guides.inventory.client;
14
15import java.util.Properties;
16
17import jakarta.ws.rs.GET;
18import jakarta.ws.rs.Path;
19import jakarta.ws.rs.ProcessingException;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22
23import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
24import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
25
26// tag::RegisterRestClient[]
27@RegisterRestClient(configKey = "systemClient",
28                     baseUri = "http://localhost:9080/system")
29// end::RegisterRestClient[]
30// tag::RegisterProvider[]
31@RegisterProvider(UnknownUriExceptionMapper.class)
32// end::RegisterProvider[]
33@Path("/properties")
34// tag::SystemClient[]
35// tag::AutoCloseable[]
36public interface SystemClient extends AutoCloseable {
37// end::AutoCloseable[]
38
39  @GET
40  // tag::Produces[]
41  @Produces(MediaType.APPLICATION_JSON)
42  // end::Produces[]
43  // tag::getProperties[]
44  Properties getProperties() throws UnknownUriException, ProcessingException;
45  // end::getProperties[]
46}
47// end::SystemClient[]
48// end::client[]

The MicroProfile Rest Client feature automatically builds and generates a client implementation based on what is defined in the SystemClient interface. There is no need to set up the client and connect with the remote service.

Notice the SystemClient interface inherits the 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 MicroProfile Rest Client specification for more details.

When the getProperties() method is invoked, the SystemClient instance sends a GET request to the <baseUrl>/properties endpoint, where <baseUrl> is the default base URL of the system service. You will see how to configure the base URL in the next section.

The @Produces annotation specifies the media (MIME) type of the expected response. The default value is MediaType.APPLICATION_JSON.

The @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 SystemClient interface, add a response exception mapper as a provider to map the 404 response code with the UnknownUriException exception.

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 @RegisterProvider annotation registers the UnknownUriExceptionMapper response exception mapper. An exception mapper maps various response codes from the remote service to throwable exceptions.

SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::client[]
13package io.openliberty.guides.inventory.client;
14
15import java.util.Properties;
16
17import jakarta.ws.rs.GET;
18import jakarta.ws.rs.Path;
19import jakarta.ws.rs.ProcessingException;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22
23import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
24import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
25
26// tag::RegisterRestClient[]
27@RegisterRestClient(configKey = "systemClient",
28                     baseUri = "http://localhost:9080/system")
29// end::RegisterRestClient[]
30// tag::RegisterProvider[]
31@RegisterProvider(UnknownUriExceptionMapper.class)
32// end::RegisterProvider[]
33@Path("/properties")
34// tag::SystemClient[]
35// tag::AutoCloseable[]
36public interface SystemClient extends AutoCloseable {
37// end::AutoCloseable[]
38
39  @GET
40  // tag::Produces[]
41  @Produces(MediaType.APPLICATION_JSON)
42  // end::Produces[]
43  // tag::getProperties[]
44  Properties getProperties() throws UnknownUriException, ProcessingException;
45  // end::getProperties[]
46}
47// end::SystemClient[]
48// end::client[]

Implement the actual exception class and the mapper class to see how this mechanism works.

Create the UnknownUriException class.
src/main/java/io/openliberty/guides/inventory/client/UnknownUriException.java

UnknownUriException.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory.client;
13
14// tag::exception[]
15public class UnknownUriException extends Exception {
16
17  private static final long serialVersionUID = 1L;
18
19  public UnknownUriException() {
20    super();
21  }
22
23  public UnknownUriException(String message) {
24    super(message);
25  }
26}
27// end::exception[]

Now, link the UnknownUriException class with the corresponding response code through a ResponseExceptionMapper mapper class.

Create the UnknownUriExceptionMapper class.
src/main/java/io/openliberty/guides/inventory/client/UnknownUriExceptionMapper.java

UnknownUriExceptionMapper.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::mapper[]
13package io.openliberty.guides.inventory.client;
14
15import java.util.logging.Logger;
16import jakarta.ws.rs.core.MultivaluedMap;
17import jakarta.ws.rs.core.Response;
18import jakarta.ws.rs.ext.Provider;
19import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
20
21@Provider
22public class UnknownUriExceptionMapper
23    implements ResponseExceptionMapper<UnknownUriException> {
24  Logger LOG = Logger.getLogger(UnknownUriExceptionMapper.class.getName());
25
26  @Override
27  // tag::handles[]
28  public boolean handles(int status, MultivaluedMap<String, Object> headers) {
29    LOG.info("status = " + status);
30    return status == 404;
31  }
32  // end::handles[]
33
34  @Override
35  // tag::toThrowable[]
36  public UnknownUriException toThrowable(Response response) {
37    return new UnknownUriException();
38  }
39  // end::toThrowable[]
40}
41// end::mapper[]

The handles() method inspects the HTTP response code to determine whether an exception is thrown for the specific response, and the toThrowable() method returns the mapped exception.

Injecting the client with dependency injection

Now, instantiate the 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 SystemClient with CDI annotations. CDI injection simplifies the process of bootstrapping the client.

First, you need to define the base URL of the SystemClient instance. Configure the default base URL with the MicroProfile Config feature. This feature is enabled for you in the server.xml file.

Create the configuration file.
src/main/webapp/META-INF/microprofile-config.properties

microprofile-config.properties

1# tag::config[]
2# tag::baseUri[]
3systemClient/mp-rest/uri=http://localhost:9080/system
4# end::baseUri[]
5# end::config[]

The mp-rest/uri base URL config property is configured to the default http://localhost:9080/system URL.

This configuration is automatically picked up by the MicroProfile Config API.

Look at the annotations in the SystemClient interface again.

SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12// tag::client[]
13package io.openliberty.guides.inventory.client;
14
15import java.util.Properties;
16
17import jakarta.ws.rs.GET;
18import jakarta.ws.rs.Path;
19import jakarta.ws.rs.ProcessingException;
20import jakarta.ws.rs.Produces;
21import jakarta.ws.rs.core.MediaType;
22
23import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
24import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
25
26// tag::RegisterRestClient[]
27@RegisterRestClient(configKey = "systemClient",
28                     baseUri = "http://localhost:9080/system")
29// end::RegisterRestClient[]
30// tag::RegisterProvider[]
31@RegisterProvider(UnknownUriExceptionMapper.class)
32// end::RegisterProvider[]
33@Path("/properties")
34// tag::SystemClient[]
35// tag::AutoCloseable[]
36public interface SystemClient extends AutoCloseable {
37// end::AutoCloseable[]
38
39  @GET
40  // tag::Produces[]
41  @Produces(MediaType.APPLICATION_JSON)
42  // end::Produces[]
43  // tag::getProperties[]
44  Properties getProperties() throws UnknownUriException, ProcessingException;
45  // end::getProperties[]
46}
47// end::SystemClient[]
48// end::client[]

The @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 configKey value in the @RegisterRestClient annotation replaces the fully-qualified classname of the properties in the microprofile-config.properties configuration file. For example, the <fully-qualified classname>/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 baseUri value can also be set in the @RegisterRestClient annotation. However, this value will be overridden by the base URI property defined in the 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 @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 SystemClient interface into the InventoryManager class, which is another CDI managed bean.

Replace the InventoryManager class.
src/main/java/io/openliberty/guides/inventory/InventoryManager.java

InventoryManager.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2024 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::manager[]
 13package io.openliberty.guides.inventory;
 14
 15import java.net.ConnectException;
 16import java.net.URI;
 17import java.net.UnknownHostException;
 18import java.util.ArrayList;
 19import java.util.Collections;
 20import java.util.List;
 21import java.util.Properties;
 22
 23import jakarta.enterprise.context.ApplicationScoped;
 24import jakarta.inject.Inject;
 25import jakarta.ws.rs.ProcessingException;
 26
 27import org.apache.commons.lang3.exception.ExceptionUtils;
 28import org.eclipse.microprofile.rest.client.RestClientBuilder;
 29import org.eclipse.microprofile.rest.client.inject.RestClient;
 30import org.eclipse.microprofile.config.inject.ConfigProperty;
 31
 32import io.openliberty.guides.inventory.client.SystemClient;
 33import io.openliberty.guides.inventory.client.UnknownUriException;
 34import io.openliberty.guides.inventory.client.UnknownUriExceptionMapper;
 35import io.openliberty.guides.inventory.model.InventoryList;
 36import io.openliberty.guides.inventory.model.SystemData;
 37
 38// tag::ApplicationScoped[]
 39@ApplicationScoped
 40// end::ApplicationScoped[]
 41public class InventoryManager {
 42
 43  private List<SystemData> systems = Collections.synchronizedList(
 44                                       new ArrayList<SystemData>());
 45
 46  @Inject
 47  @ConfigProperty(name = "http.port")
 48  String HTTP_PORT;
 49
 50  // tag::Inject[]
 51  @Inject
 52  // end::Inject[]
 53  // tag::RestClient[]
 54  @RestClient
 55  // end::RestClient[]
 56  // tag::SystemClient[]
 57  private SystemClient defaultRestClient;
 58  // end::SystemClient[]
 59
 60  public Properties get(String hostname) {
 61    Properties properties = null;
 62    if (hostname.equals("localhost")) {
 63      properties = getPropertiesWithDefaultHostName();
 64    } else {
 65      properties = getPropertiesWithGivenHostName(hostname);
 66    }
 67
 68    return properties;
 69  }
 70
 71  public void add(String hostname, Properties systemProps) {
 72    Properties props = new Properties();
 73    props.setProperty("os.name", systemProps.getProperty("os.name"));
 74    props.setProperty("user.name", systemProps.getProperty("user.name"));
 75
 76    SystemData host = new SystemData(hostname, props);
 77    if (!systems.contains(host)) {
 78      systems.add(host);
 79    }
 80  }
 81
 82  public InventoryList list() {
 83    return new InventoryList(systems);
 84  }
 85
 86  // tag::getPropertiesWithDefaultHostName[]
 87  private Properties getPropertiesWithDefaultHostName() {
 88    try {
 89      // tag::defaultRCGetProperties[]
 90      return defaultRestClient.getProperties();
 91      // end::defaultRCGetProperties[]
 92    } catch (UnknownUriException e) {
 93      System.err.println("The given URI is not formatted correctly.");
 94    } catch (ProcessingException ex) {
 95      handleProcessingException(ex);
 96    }
 97    return null;
 98  }
 99  // end::getPropertiesWithDefaultHostName[]
100
101  // tag::getPropertiesWithGivenHostName[]
102  private Properties getPropertiesWithGivenHostName(String hostname) {
103    String customURIString = "http://" + hostname + ":" + HTTP_PORT + "/system";
104    URI customURI = null;
105    try {
106      customURI = URI.create(customURIString);
107      // tag::customRestClientBuilder[]
108      SystemClient customRestClient = RestClientBuilder.newBuilder()
109                                        .baseUri(customURI)
110                                        .register(UnknownUriExceptionMapper.class)
111                                        .build(SystemClient.class);
112      // end::customRestClientBuilder[]
113      // tag::customRCGetProperties[]
114      return customRestClient.getProperties();
115      // end::customRCGetProperties[]
116    } catch (ProcessingException ex) {
117      handleProcessingException(ex);
118    } catch (UnknownUriException e) {
119      System.err.println("The given URI is unreachable.");
120    }
121    return null;
122  }
123  // end::getPropertiesWithGivenHostName[]
124
125  private void handleProcessingException(ProcessingException ex) {
126    Throwable rootEx = ExceptionUtils.getRootCause(ex);
127    if (rootEx != null && (rootEx instanceof UnknownHostException
128        || rootEx instanceof ConnectException)) {
129      System.err.println("The specified host is unknown.");
130    } else {
131      throw ex;
132    }
133  }
134
135}
136// end::manager[]

@Inject and @RestClient annotations inject an instance of the SystemClient called defaultRestClient to the InventoryManager class.

Because the InventoryManager class is @ApplicationScoped, and the 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 getPropertiesWithDefaultHostName() helper function to fetch system properties. The helper function invokes the system service by calling the defaultRestClient.getProperties() method.

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonp-2.1</feature>
 6    <feature>jsonb-3.0</feature>
 7    <feature>cdi-4.0</feature>
 8    <!-- tag::mpConfig[] -->
 9    <feature>mpConfig-3.1</feature>
10    <!-- end::mpConfig[] -->
11    <!-- tag::mpRestClient[] -->
12    <feature>mpRestClient-3.0</feature>
13    <!-- end::mpRestClient[] -->
14  </featureManager>
15
16  <variable name="http.port" defaultValue="9080"/>
17  <variable name="https.port" defaultValue="9443"/>
18  
19  <httpEndpoint host="*" httpPort="${http.port}" httpsPort="${https.port}"
20                id="defaultHttpEndpoint"/>
21
22  <webApplication location="guide-microprofile-rest-client.war" contextRoot="/"/>
23</server>

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 getPropertiesWithGivenHostName() method in the src/main/java/io/openliberty/guides/inventory/InventoryManager.java file.

InventoryManager.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2024 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::manager[]
 13package io.openliberty.guides.inventory;
 14
 15import java.net.ConnectException;
 16import java.net.URI;
 17import java.net.UnknownHostException;
 18import java.util.ArrayList;
 19import java.util.Collections;
 20import java.util.List;
 21import java.util.Properties;
 22
 23import jakarta.enterprise.context.ApplicationScoped;
 24import jakarta.inject.Inject;
 25import jakarta.ws.rs.ProcessingException;
 26
 27import org.apache.commons.lang3.exception.ExceptionUtils;
 28import org.eclipse.microprofile.rest.client.RestClientBuilder;
 29import org.eclipse.microprofile.rest.client.inject.RestClient;
 30import org.eclipse.microprofile.config.inject.ConfigProperty;
 31
 32import io.openliberty.guides.inventory.client.SystemClient;
 33import io.openliberty.guides.inventory.client.UnknownUriException;
 34import io.openliberty.guides.inventory.client.UnknownUriExceptionMapper;
 35import io.openliberty.guides.inventory.model.InventoryList;
 36import io.openliberty.guides.inventory.model.SystemData;
 37
 38// tag::ApplicationScoped[]
 39@ApplicationScoped
 40// end::ApplicationScoped[]
 41public class InventoryManager {
 42
 43  private List<SystemData> systems = Collections.synchronizedList(
 44                                       new ArrayList<SystemData>());
 45
 46  @Inject
 47  @ConfigProperty(name = "http.port")
 48  String HTTP_PORT;
 49
 50  // tag::Inject[]
 51  @Inject
 52  // end::Inject[]
 53  // tag::RestClient[]
 54  @RestClient
 55  // end::RestClient[]
 56  // tag::SystemClient[]
 57  private SystemClient defaultRestClient;
 58  // end::SystemClient[]
 59
 60  public Properties get(String hostname) {
 61    Properties properties = null;
 62    if (hostname.equals("localhost")) {
 63      properties = getPropertiesWithDefaultHostName();
 64    } else {
 65      properties = getPropertiesWithGivenHostName(hostname);
 66    }
 67
 68    return properties;
 69  }
 70
 71  public void add(String hostname, Properties systemProps) {
 72    Properties props = new Properties();
 73    props.setProperty("os.name", systemProps.getProperty("os.name"));
 74    props.setProperty("user.name", systemProps.getProperty("user.name"));
 75
 76    SystemData host = new SystemData(hostname, props);
 77    if (!systems.contains(host)) {
 78      systems.add(host);
 79    }
 80  }
 81
 82  public InventoryList list() {
 83    return new InventoryList(systems);
 84  }
 85
 86  // tag::getPropertiesWithDefaultHostName[]
 87  private Properties getPropertiesWithDefaultHostName() {
 88    try {
 89      // tag::defaultRCGetProperties[]
 90      return defaultRestClient.getProperties();
 91      // end::defaultRCGetProperties[]
 92    } catch (UnknownUriException e) {
 93      System.err.println("The given URI is not formatted correctly.");
 94    } catch (ProcessingException ex) {
 95      handleProcessingException(ex);
 96    }
 97    return null;
 98  }
 99  // end::getPropertiesWithDefaultHostName[]
100
101  // tag::getPropertiesWithGivenHostName[]
102  private Properties getPropertiesWithGivenHostName(String hostname) {
103    String customURIString = "http://" + hostname + ":" + HTTP_PORT + "/system";
104    URI customURI = null;
105    try {
106      customURI = URI.create(customURIString);
107      // tag::customRestClientBuilder[]
108      SystemClient customRestClient = RestClientBuilder.newBuilder()
109                                        .baseUri(customURI)
110                                        .register(UnknownUriExceptionMapper.class)
111                                        .build(SystemClient.class);
112      // end::customRestClientBuilder[]
113      // tag::customRCGetProperties[]
114      return customRestClient.getProperties();
115      // end::customRCGetProperties[]
116    } catch (ProcessingException ex) {
117      handleProcessingException(ex);
118    } catch (UnknownUriException e) {
119      System.err.println("The given URI is unreachable.");
120    }
121    return null;
122  }
123  // end::getPropertiesWithGivenHostName[]
124
125  private void handleProcessingException(ProcessingException ex) {
126    Throwable rootEx = ExceptionUtils.getRootCause(ex);
127    if (rootEx != null && (rootEx instanceof UnknownHostException
128        || rootEx instanceof ConnectException)) {
129      System.err.println("The specified host is unknown.");
130    } else {
131      throw ex;
132    }
133  }
134
135}
136// end::manager[]

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 RestClientBuilder builder with the new URL, registers the response exception mapper, and builds the SystemClient instance.

Similarly, call the customRestClient.getProperties() method to invoke the system service.

Running the application

You started the Open Liberty in dev mode at the beginning of the guide, so all the changes were automatically picked up.

When the Liberty instance is running, select either approach to fetch your system properties:

  • 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.

Testing the application

Create the RestClientIT class.
src/test/java/it/io/openliberty/guides/client/RestClientIT.java

RestClientIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12// tag::testClass[]
 13package it.io.openliberty.guides.client;
 14
 15import static org.junit.jupiter.api.Assertions.assertEquals;
 16import jakarta.json.JsonObject;
 17import jakarta.ws.rs.client.Client;
 18import jakarta.ws.rs.client.ClientBuilder;
 19import jakarta.ws.rs.core.Response;
 20import jakarta.ws.rs.client.WebTarget;
 21import org.junit.jupiter.api.AfterEach;
 22import org.junit.jupiter.api.BeforeEach;
 23import org.junit.jupiter.api.BeforeAll;
 24import org.junit.jupiter.api.Test;
 25import java.net.InetAddress;
 26import java.net.UnknownHostException;
 27
 28public class RestClientIT {
 29
 30  private static String port;
 31
 32  private Client client;
 33
 34  private final String INVENTORY_SYSTEMS = "inventory/systems";
 35
 36  @BeforeAll
 37  public static void oneTimeSetup() {
 38    port = System.getProperty("http.port");
 39  }
 40
 41  @BeforeEach
 42  public void setup() {
 43    client = ClientBuilder.newClient();
 44  }
 45
 46  @AfterEach
 47  public void teardown() {
 48    client.close();
 49  }
 50
 51  @Test
 52  public void testSuite() {
 53    this.testDefaultLocalhost();
 54    this.testRestClientBuilder();
 55  }
 56
 57  // tag::testDefaultLocalhost[]
 58  public void testDefaultLocalhost() {
 59    String hostname = "localhost";
 60
 61    String url = "http://localhost:" + port + "/" + INVENTORY_SYSTEMS + "/" + hostname;
 62
 63    JsonObject obj = fetchProperties(url);
 64
 65    assertEquals(System.getProperty("os.name"), obj.getString("os.name"),
 66                 "The system property for the local and remote JVM should match");
 67  }
 68  // end::testDefaultLocalhost[]
 69
 70  // tag::testRestClientBuilder[]
 71  public void testRestClientBuilder() {
 72    String hostname = null;
 73    try {
 74      hostname = InetAddress.getLocalHost().getHostAddress();
 75    } catch (UnknownHostException e) {
 76      System.err.println("Unknown Host.");
 77    }
 78
 79    String url = "http://localhost:" + port + "/" + INVENTORY_SYSTEMS + "/" + hostname;
 80
 81    JsonObject obj = fetchProperties(url);
 82
 83    assertEquals(System.getProperty("os.name"), obj.getString("os.name"),
 84                 "The system property for the local and remote JVM should match");
 85  }
 86  // end::testRestClientBuilder[]
 87
 88  private JsonObject fetchProperties(String url) {
 89    WebTarget target = client.target(url);
 90    Response response = target.request().get();
 91
 92    assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
 93
 94    JsonObject obj = response.readEntity(JsonObject.class);
 95    response.close();
 96    return obj;
 97  }
 98
 99}
100// end::testClass[]

Each test case tests one of the methods for instantiating a RESTful client.

The testDefaultLocalhost() test fetches and compares system properties from the http://localhost:9080/inventory/systems/localhost URL.

The 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.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

-------------------------------------------------------
 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.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

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 Hostname verification with SSL on Open Liberty and MicroProfile Rest Client blog and the 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.

Guide Attribution

Consuming RESTful services with template interfaces by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star