Creating reactive Java microservices

duration 20 minutes

Prerequisites:

Learn how to write reactive Java microservices using MicroProfile Reactive Messaging.

What you’ll learn

You will learn how to build reactive microservices that can send requests to other microservices, and asynchronously receive and process the responses. You will use an external messaging system to handle the asynchronous messages that are sent and received between the microservices as streams of events. MicroProfile Reactive Messaging makes it easy to write and configure your application to send, receive, and process the events efficiently.

Asynchronous messaging between microservices

Asynchronous communication between microservices can be used to build reactive and responsive applications. By decoupling the requests sent by a microservice from the responses that it receives, the microservice is not blocked from performing other tasks while waiting for the requested data to become available. Imagine asynchronous communication as a restaurant. A waiter might come to your table and take your order. While you are waiting for your food to be prepared, that waiter serves other tables and takes their orders too. When your food is ready, the waiter brings your food to the table and then continues to serve the other tables. If the waiter were to operate synchronously, they must take your order and then wait until they deliver your food before serving any other tables. In microservices, a request call from a REST client to another microservice can be time-consuming because the network might be slow, or the other service might be overwhelmed with requests and can’t respond quickly. But in an asynchronous system, the microservice sends a request to another microservice and continues to send other calls and to receive and process other responses until it receives a response to the original request.

What is MicroProfile Reactive Messaging?

MicroProfile Reactive Messaging provides an easy way to asynchronously send, receive, and process messages that are received as continuous streams of events. You simply annotate application beans' methods and Open Liberty converts the annotated methods to reactive streams-compatible publishers, subscribers, and processors and connects them up to each other. MicroProfile Reactive Messaging provides a Connector API so that your methods can be connected to external messaging systems that produce and consume the streams of events, such as Apache Kafka.

The application in this guide consists of two microservices, system and inventory. Every 15 seconds, the system microservice calculates and publishes an event that contains its current average system load. The inventory microservice subscribes to that information so that it can keep an updated list of all the systems and their current system loads. The current inventory of systems can be accessed via the /systems REST endpoint. You’ll create the system and inventory microservices using MicroProfile Reactive Messaging.

Reactive system inventory

Additional prerequisites

You need to have Docker installed. For installation instructions, refer to the official Docker documentation. You will build and run the microservices in Docker containers. An installation of Apache Kafka is provided in another Docker container.

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-reactive-messaging.git
cd guide-microprofile-reactive-messaging

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.

Creating the producer in the system microservice

Navigate to the start directory to begin.

The system microservice is the producer of the messages that are published to the Kafka messaging system as a stream of events. Every 15 seconds, the system microservice publishes an event that contains its calculation of the average system load (its CPU usage) for the last minute.

Create the SystemService class.
system/src/main/java/io/openliberty/guides/system/SystemService.java

SystemService.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2020 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 v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.system;
14
15import java.lang.management.ManagementFactory;
16import java.lang.management.OperatingSystemMXBean;
17import java.net.InetAddress;
18import java.net.UnknownHostException;
19import java.util.concurrent.TimeUnit;
20
21import javax.enterprise.context.ApplicationScoped;
22
23import org.eclipse.microprofile.reactive.messaging.Outgoing;
24import org.reactivestreams.Publisher;
25
26import io.openliberty.guides.models.SystemLoad;
27import io.reactivex.rxjava3.core.Flowable;
28
29@ApplicationScoped
30public class SystemService {
31
32    private static final OperatingSystemMXBean osMean = 
33            ManagementFactory.getOperatingSystemMXBean();
34    private static String hostname = null;
35
36    private static String getHostname() {
37        if (hostname == null) {
38            try {
39                return InetAddress.getLocalHost().getHostName();
40            } catch (UnknownHostException e) {
41                return System.getenv("HOSTNAME");
42            }
43        }
44        return hostname;
45    }
46
47    // tag::sendSystemLoad[]
48    // tag::publishSystemLoad[]
49    @Outgoing("systemLoad")
50    // end::publishSystemLoad[]
51    public Publisher<SystemLoad> sendSystemLoad() {
52        // tag::flowableInterval[]
53        return Flowable.interval(15, TimeUnit.SECONDS)
54                .map((interval -> new SystemLoad(getHostname(),
55                        new Double(osMean.getSystemLoadAverage()))));
56        // end::flowableInterval[]
57    }
58    // end::sendSystemLoad[]
59
60}

system/microprofile-config.properties

 1# Kafka connection details
 2# tag::kafkaConfig[]
 3mp.messaging.connector.liberty-kafka.bootstrap.servers=localhost:9093
 4# end::kafkaConfig[]
 5
 6# systemLoad stream
 7# tag::systemLoad[]
 8# tag::kafka1[]
 9mp.messaging.outgoing.systemLoad.connector=liberty-kafka
10# end::kafka1[]
11# tag::topic1[]
12mp.messaging.outgoing.systemLoad.topic=system.load
13# end::topic1[]
14# tag::serializer1[]
15mp.messaging.outgoing.systemLoad.key.serializer=org.apache.kafka.common.serialization.StringSerializer
16# end::serializer1[]
17# tag::serializerVal1[]
18mp.messaging.outgoing.systemLoad.value.serializer=io.openliberty.guides.models.SystemLoad$SystemLoadSerializer
19# end::serializerVal1[]
20# end::systemLoad[]

The SystemService class contains a Publisher method that is called sendSystemLoad(), which calculates and returns the average system load. The @Outgoing annotation on the sendSystemLoad() method indicates that the method publishes its calculation as a message on a topic in the Kafka messaging system. The Flowable.interval() method from rxJava is used to set the frequency of how often the system service publishes the calculation to the event stream.

The messages are transported between the service and the Kafka messaging system through a channel called systemLoad. The name of the channel to use is set in the @Outgoing("systemLoad") annotation. Later in the guide, you will configure the service so that any messages sent by the system service through the systemLoad channel are published on a topic called system.load, as shown in the following diagram:

Reactive system publisher

Creating the consumer in the inventory microservice

The inventory microservice records in its inventory the average system load information that it received from potentially multiple instances of the system service.

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

InventoryResource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2020 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 v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.inventory;
 14
 15import java.util.List;
 16import java.util.Optional;
 17import java.util.Properties;
 18import java.util.logging.Logger;
 19import java.util.stream.Collectors;
 20
 21import javax.enterprise.context.ApplicationScoped;
 22import javax.inject.Inject;
 23import javax.ws.rs.DELETE;
 24import javax.ws.rs.GET;
 25import javax.ws.rs.Path;
 26import javax.ws.rs.PathParam;
 27import javax.ws.rs.Produces;
 28import javax.ws.rs.core.MediaType;
 29import javax.ws.rs.core.Response;
 30
 31import org.eclipse.microprofile.reactive.messaging.Incoming;
 32
 33import io.openliberty.guides.models.SystemLoad;
 34
 35@ApplicationScoped
 36//tag::inventoryEndPoint[]
 37@Path("/inventory")
 38//end::inventoryEndPoint[]
 39public class InventoryResource {
 40
 41    private static Logger logger = Logger.getLogger(InventoryResource.class.getName());
 42
 43    @Inject
 44    private InventoryManager manager;
 45    
 46    @GET
 47    @Path("/systems")
 48    @Produces(MediaType.APPLICATION_JSON)
 49    public Response getSystems() {
 50        List<Properties> systems = manager.getSystems()
 51                .values()
 52                .stream()
 53                .collect(Collectors.toList());
 54        return Response
 55                .status(Response.Status.OK)
 56                .entity(systems)
 57                .build();
 58    }
 59
 60    @GET
 61    @Path("/systems/{hostname}")
 62    @Produces(MediaType.APPLICATION_JSON)
 63    public Response getSystem(@PathParam("hostname") String hostname) {
 64        Optional<Properties> system = manager.getSystem(hostname);
 65        if (system.isPresent()) {
 66            return Response
 67                    .status(Response.Status.OK)
 68                    .entity(system)
 69                    .build();
 70        }
 71        return Response
 72                .status(Response.Status.NOT_FOUND)
 73                .entity("hostname does not exist.")
 74                .build();
 75    }
 76
 77    @DELETE
 78    @Produces(MediaType.APPLICATION_JSON)
 79    public Response resetSystems() {
 80        manager.resetSystems();
 81        return Response
 82                .status(Response.Status.OK)
 83                .build();
 84    }
 85
 86    // tag::updateStatus[]
 87    // tag::systemLoad[]
 88    @Incoming("systemLoad")
 89    // end::systemLoad[]
 90    public void updateStatus(SystemLoad sl)  {
 91        String hostname = sl.hostname;
 92        if (manager.getSystem(hostname).isPresent()) {
 93            manager.updateCpuStatus(hostname, sl.loadAverage);
 94            logger.info("Host " + hostname + " was updated: " + sl);
 95        } else {
 96            manager.addSystem(hostname, sl.loadAverage);
 97            logger.info("Host " + hostname + " was added: " + sl);
 98        }
 99    }
100    // end::updateStatus[]
101}

inventory/microprofile-config.properties

 1# Kafka connection details
 2# tag::kafkaConfig[]
 3mp.messaging.connector.liberty-kafka.bootstrap.servers=localhost:9093
 4# end::kafkaConfig[]
 5
 6# systemLoad stream
 7# tag::systemLoad[]
 8# tag::kafka1[]
 9mp.messaging.incoming.systemLoad.connector=liberty-kafka
10# end::kafka1[]
11# tag::topic1[]
12mp.messaging.incoming.systemLoad.topic=system.load
13# end::topic1[]
14# tag::deserializer1[]
15mp.messaging.incoming.systemLoad.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
16# end::deserializer1[]
17# tag::deserializerVal1[]
18mp.messaging.incoming.systemLoad.value.deserializer=io.openliberty.guides.models.SystemLoad$SystemLoadDeserializer
19# end::deserializerVal1[]
20# tag::group1[]
21mp.messaging.incoming.systemLoad.group.id=system-load-status
22# end::group1[]
23# end::systemLoad[]

The inventory microservice receives the message from the system microservice over the @Incoming("systemLoad") channel. The properties of this channel are defined in the microprofile-config.properties file. The inventory microservice is also a RESTful service that is served at the /inventory endpoint.

The InventoryResource class contains a method called updateStatus(), which receives the message that contains the average system load and updates its existing inventory of systems and their average system load. The @Incoming("systemLoad") annotation on the updateStatus() method indicates that the method retrieves the average system load information by connecting to the channel called systemLoad. Later in the guide, you will configure the service so that any messages sent by the system service through the systemLoad channel are retrieved from a topic called system.load, as shown in the following diagram:

Reactive system inventory detail

Configuring the MicroProfile Reactive Messaging connectors for Kafka

The system and inventory services exchange messages with the external messaging system through a channel. The MicroProfile Reactive Messaging Connector API makes it easy to connect each service to the channel. You just need to add configuration keys in a properties file for each of the services. These configuration keys define properties such as the name of the channel and the topic in the Kafka messaging system. Open Liberty includes the liberty-kafka connector for sending and receiving messages from Apache Kafka.

The system and inventory microservices each have a MicroProfile Config properties file to define the properties of their outgoing and incoming streams.

Create the system/microprofile-config.properties file.
system/src/main/resources/META-INF/microprofile-config.properties

system/microprofile-config.properties

 1# Kafka connection details
 2# tag::kafkaConfig[]
 3mp.messaging.connector.liberty-kafka.bootstrap.servers=localhost:9093
 4# end::kafkaConfig[]
 5
 6# systemLoad stream
 7# tag::systemLoad[]
 8# tag::kafka1[]
 9mp.messaging.outgoing.systemLoad.connector=liberty-kafka
10# end::kafka1[]
11# tag::topic1[]
12mp.messaging.outgoing.systemLoad.topic=system.load
13# end::topic1[]
14# tag::serializer1[]
15mp.messaging.outgoing.systemLoad.key.serializer=org.apache.kafka.common.serialization.StringSerializer
16# end::serializer1[]
17# tag::serializerVal1[]
18mp.messaging.outgoing.systemLoad.value.serializer=io.openliberty.guides.models.SystemLoad$SystemLoadSerializer
19# end::serializerVal1[]
20# end::systemLoad[]

The mp.messaging.connector.liberty-kafka.bootstrap.servers property configures the hostname and port for connecting to the Kafka server. The system microservice uses an outgoing connector to send messages through the systemLoad channel to the system.load topic in the Kafka message broker so that the inventory microservices can consume the messages. The key.serializer and value.serializer properties characterize how to serialize the messages. The SystemLoadSerializer class implements the logic for turning a SystemLoad object into JSON and is configured as the value.serializer.

The inventory microservice uses a similar microprofile-config.properties configuration to define its required incoming stream.

Create the inventory/microprofile-config.properties file.
inventory/src/main/resources/META-INF/microprofile-config.properties

inventory/microprofile-config.properties

 1# Kafka connection details
 2# tag::kafkaConfig[]
 3mp.messaging.connector.liberty-kafka.bootstrap.servers=localhost:9093
 4# end::kafkaConfig[]
 5
 6# systemLoad stream
 7# tag::systemLoad[]
 8# tag::kafka1[]
 9mp.messaging.incoming.systemLoad.connector=liberty-kafka
10# end::kafka1[]
11# tag::topic1[]
12mp.messaging.incoming.systemLoad.topic=system.load
13# end::topic1[]
14# tag::deserializer1[]
15mp.messaging.incoming.systemLoad.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
16# end::deserializer1[]
17# tag::deserializerVal1[]
18mp.messaging.incoming.systemLoad.value.deserializer=io.openliberty.guides.models.SystemLoad$SystemLoadDeserializer
19# end::deserializerVal1[]
20# tag::group1[]
21mp.messaging.incoming.systemLoad.group.id=system-load-status
22# end::group1[]
23# end::systemLoad[]

The inventory microservice uses an incoming connector to receive messages through the systemLoad channel. The messages were published by the system microservice to the system.load topic in the Kafka message broker. The key.deserializer and value.deserializer properties define how to deserialize the messages. The SystemLoadDeserializer class implements the logic for turning JSON into a SystemLoad object and is configured as the value.deserializer. The group.id property defines a unique name for the consumer group. A consumer group is a collection of consumers who share a common identifier for the group. You can also view a consumer group as the various machines that ingest from the Kafka topics. All of these properties are required by the Apache Kafka Producer Configs and Apache Kafka Consumer Configs.

Configuring the server

To run the services, the Open Liberty server on which each service runs needs to be correctly configured. Relevant features, including the MicroProfile Reactive Messaging feature, must be enabled for the system and inventory services.

Create the system/server.xml configuration file.
system/src/main/liberty/config/server.xml

server.xml

 1<server description="System Service">
 2
 3  <featureManager>
 4    <feature>cdi-2.0</feature>
 5    <feature>concurrent-1.0</feature>
 6    <feature>jsonb-1.0</feature>
 7    <feature>mpHealth-2.2</feature>
 8    <feature>mpConfig-1.4</feature>
 9    <!-- tag::featureMP[] -->
10    <feature>mpReactiveMessaging-1.0</feature>
11    <!-- end::featureMP[] -->
12  </featureManager>
13
14  <variable name="default.http.port" defaultValue="9083"/>
15  <variable name="default.https.port" defaultValue="9446"/>
16
17  <httpEndpoint host="*" httpPort="${default.http.port}"
18      httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
19
20  <webApplication location="system.war" contextRoot="/"/>
21</server>

server.xml

 1<server description="Inventory Service">
 2
 3  <featureManager>
 4    <feature>jaxrs-2.1</feature>
 5    <feature>cdi-2.0</feature>
 6    <feature>concurrent-1.0</feature>
 7    <feature>jsonb-1.0</feature>
 8    <feature>mpHealth-2.2</feature>
 9    <feature>mpConfig-1.4</feature>
10    <feature>mpReactiveMessaging-1.0</feature>
11  </featureManager>
12
13  <variable name="default.http.port" defaultValue="9085"/>
14  <variable name="default.https.port" defaultValue="9448"/>
15
16  <httpEndpoint host="*" httpPort="${default.http.port}" httpsPort="${default.https.port}" id="defaultHttpEndpoint"/>
17
18  <webApplication location="inventory.war" contextRoot="/"/>
19</server>

The server.xml file is already configured for the inventory microservice.

Building and running the application

Build the system and inventory microservices using Maven and then run them in Docker containers.

Create the Maven configuration file.
system/pom.xml

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0"
  3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5    <modelVersion>4.0.0</modelVersion>
  6
  7    <groupId>io.openliberty.guides</groupId>
  8    <artifactId>system</artifactId>
  9    <version>1.0-SNAPSHOT</version>
 10    <packaging>war</packaging>
 11
 12    <properties>
 13        <maven.compiler.source>1.8</maven.compiler.source>
 14        <maven.compiler.target>1.8</maven.compiler.target>
 15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 16        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 17        <!-- Liberty configuration -->
 18        <liberty.var.default.http.port>9083</liberty.var.default.http.port>
 19        <liberty.var.default.https.port>9446</liberty.var.default.https.port>
 20    </properties>
 21
 22    <dependencies>
 23        <!-- Provided dependencies -->
 24        <dependency>
 25            <groupId>jakarta.platform</groupId>
 26            <artifactId>jakarta.jakartaee-api</artifactId>
 27            <version>8.0.0</version>
 28            <scope>provided</scope>
 29        </dependency>
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>3.3</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        <!-- tag::reactiveMessaging[] -->
 38        <dependency>
 39            <groupId>org.eclipse.microprofile.reactive.messaging</groupId>
 40            <artifactId>microprofile-reactive-messaging-api</artifactId>
 41            <version>1.0</version>
 42            <scope>provided</scope>
 43        </dependency>
 44        <!-- end::reactiveMessaging[] -->
 45        <!-- Required dependencies -->
 46        <dependency>
 47            <groupId>io.openliberty.guides</groupId>
 48            <artifactId>models</artifactId>
 49            <version>1.0-SNAPSHOT</version>
 50        </dependency>
 51        <!-- tag::kafka[] -->
 52        <dependency>
 53            <groupId>org.apache.kafka</groupId>
 54            <artifactId>kafka-clients</artifactId>
 55            <version>2.8.1</version>
 56        </dependency>
 57        <!-- end::kafka[] -->
 58        <!-- tag::rxjava[] -->
 59        <dependency>
 60            <groupId>io.reactivex.rxjava3</groupId>
 61            <artifactId>rxjava</artifactId>
 62            <version>3.1.2</version>
 63        </dependency>
 64        <!-- end::rxjava[] -->
 65        <!-- For tests -->
 66        <dependency>
 67            <groupId>org.microshed</groupId>
 68            <artifactId>microshed-testing-liberty</artifactId>
 69            <version>0.9.1</version>
 70            <scope>test</scope>
 71        </dependency>
 72        <dependency>
 73            <groupId>org.testcontainers</groupId>
 74            <artifactId>kafka</artifactId>
 75            <version>1.16.2</version>
 76            <scope>test</scope>
 77        </dependency>
 78        <dependency>
 79            <groupId>org.junit.jupiter</groupId>
 80            <artifactId>junit-jupiter</artifactId>
 81            <version>5.8.1</version>
 82            <scope>test</scope>
 83        </dependency>
 84    </dependencies>
 85
 86    <build>
 87        <finalName>${project.artifactId}</finalName>
 88        <plugins>
 89            <plugin>
 90                <groupId>org.apache.maven.plugins</groupId>
 91                <artifactId>maven-war-plugin</artifactId>
 92                <version>3.3.2</version>
 93                <configuration>
 94                    <packagingExcludes>pom.xml</packagingExcludes>
 95                </configuration>
 96            </plugin>
 97
 98            <!-- Liberty plugin -->
 99            <plugin>
100                <groupId>io.openliberty.tools</groupId>
101                <artifactId>liberty-maven-plugin</artifactId>
102                <version>3.8.2</version>
103            </plugin>
104
105            <!-- Plugin to run unit tests -->
106            <plugin>
107                <groupId>org.apache.maven.plugins</groupId>
108                <artifactId>maven-surefire-plugin</artifactId>
109                <version>2.22.2</version>
110            </plugin>
111
112            <!-- Plugin to run integration tests -->
113            <plugin>
114                <groupId>org.apache.maven.plugins</groupId>
115                <artifactId>maven-failsafe-plugin</artifactId>
116                <version>2.22.2</version>
117                <executions>
118                    <execution>
119                        <id>integration-test</id>
120                        <goals>
121                            <goal>integration-test</goal>
122                        </goals>
123                        <configuration>
124                            <trimStackTrace>false</trimStackTrace>
125                        </configuration>
126                    </execution>
127                    <execution>
128                        <id>verify</id>
129                        <goals>
130                            <goal>verify</goal>
131                        </goals>
132                    </execution>
133                </executions>
134            </plugin>
135        </plugins>
136    </build>
137</project>

The pom.xml file lists the microprofile-reactive-messaging-api, kafka-clients, and rxjava dependencies.

The microprofile-reactive-messaging-api dependency is needed to enable the use of MicroProfile Reactive Messaging API. The kafka-clients dependency is added because the application needs a Kafka client to connect to the Kafka broker. The rxjava dependency is used for creating events at regular intervals.

Start your Docker environment. Dockerfiles are provided for you to use.

To build the application, run the Maven install and package goals from the command line in the start directory:

mvn -pl models install
mvn package

Run the following commands to containerize the microservices:

docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.

Next, use the provided script to start the application in Docker containers. The script creates a network for the containers to communicate with each other. It also creates containers for Kafka, Zookeeper, and the microservices in the project. For simplicity, the script starts one instance of the system service.

.\scripts\startContainers.bat
./scripts/startContainers.sh

Testing the application

The application might take some time to become available. After the application is up and running, you can access it by making a GET request to the /systems endpoint of the inventory service.

Visit the http://localhost:9085/health URL to confirm that the inventory microservice is up and running.

When both the liveness and readiness health checks are up, go to the http://localhost:9085/inventory/systems URL to access the inventory microservice. You see the CPU systemLoad property for all the systems:

{
   "hostname":"30bec2b63a96",
   "systemLoad":2.25927734375
}

You can revisit the http://localhost:9085/inventory/systems URL after a while, and you will notice the CPU systemLoad property for the systems changed.

You can use the http://localhost:9085/inventory/systems/{hostname} URL to see the CPU systemLoad property for one particular system.

In the following example, the 30bec2b63a96 value is the hostname. If you go to the http://localhost:9085/inventory/systems/30bec2b63a96 URL, you can see the CPU systemLoad property only for the 30bec2b63a96 hostname:

{
   "hostname":"30bec2b63a96",
   "systemLoad":2.25927734375
}

Tearing down the environment

Run the following script to stop the application:

.\scripts\stopContainers.bat
./scripts/stopContainers.sh

Great work! You’re done!

You just developed a reactive Java application using MicroProfile Reactive Messaging, Open Liberty, and Kafka.

Guide Attribution

Creating reactive Java microservices 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