Bidirectional communication between services using Jakarta WebSocket

duration 25 minutes

Prerequisites:

Learn how to use Jakarta WebSocket to send and receive messages between services without closing the connection.

What you’ll learn

Jakarta WebSocket enables two-way communication between client and server endpoints. First, each client makes an HTTP connection to a Jakarta WebSocket server. The server can then broadcast messages to the clients. Server-Sent Events (SSE) also enables a client to receive automatic updates from a server via an HTTP connection however WebSocket differs from Server-Sent Events in that SSE is unidirectional from server to client, whereas WebSocket is bidirectional. WebSocket also enables real-time updates over a smaller bandwidth than SSE. The connection isn’t closed meaning that the client can continue to send and receive messages with the server, without having to poll the server to receive any replies.

The application that you will build in this guide consists of the client service and the system server service. The following diagram depicts the application that is used in this guide.

Application architecture where system and client services use the Jakarta Websocket API to connect and communicate.

You’ll learn how to use the Jakarta WebSocket API to build the system service and the scheduler in the client service. The scheduler pushes messages to the system service every 10 seconds, then the system service broadcasts the messages to any connected clients. You will also learn how to use a JavaScript WebSocket object in an HTML file to build a WebSocket connection, subscribe to different events, and display the broadcasting messages from the system service in a table.

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-jakarta-websocket.git
cd guide-jakarta-websocket

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, go to the finish directory and run the following Maven goal to build the system service and deploy it to Open Liberty:

mvn -pl system liberty:run

Next, open another command-line session and run the following command to start the client service:

mvn -pl client liberty:run

After you see the following message in both command-line sessions, both your services are ready.

The defaultServer is ready to run a smarter planet.

Check out the service at the http://localhost:9080 URL. See that the table is being updated for every 10 seconds.

After you are finished checking out the application, stop both the system and client services by pressing CTRL+C in the command-line sessions where you ran them. Alternatively, you can run the following goals from the finish directory in another command-line session:

mvn -pl system liberty:stop
mvn -pl client liberty:stop

Creating the WebSocket server service

In this section, you will create the system WebSocket server service that broadcasts messages to clients.

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 command to start the system service in dev mode:

mvn -pl system liberty:dev

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

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

The system service is responsible for handling the messages produced by the client scheduler, building system load messages, and forwarding them to clients.

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

SystemService.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2022, 2023 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.system;
 13
 14import java.lang.management.ManagementFactory;
 15import java.lang.management.MemoryMXBean;
 16import java.util.Calendar;
 17import java.util.HashSet;
 18import java.util.Set;
 19import java.util.logging.Logger;
 20
 21import jakarta.json.Json;
 22import jakarta.json.JsonObject;
 23import jakarta.json.JsonObjectBuilder;
 24import jakarta.websocket.CloseReason;
 25import jakarta.websocket.OnClose;
 26import jakarta.websocket.OnError;
 27import jakarta.websocket.OnMessage;
 28import jakarta.websocket.OnOpen;
 29import jakarta.websocket.Session;
 30import jakarta.websocket.server.ServerEndpoint;
 31
 32import com.sun.management.OperatingSystemMXBean;
 33
 34// tag::serverEndpoint[]
 35@ServerEndpoint(value = "/systemLoad",
 36                decoders = { SystemLoadDecoder.class },
 37                encoders = { SystemLoadEncoder.class })
 38// end::serverEndpoint[]
 39public class SystemService {
 40
 41    private static Logger logger = Logger.getLogger(SystemService.class.getName());
 42
 43    private static Set<Session> sessions = new HashSet<>();
 44
 45    private static final OperatingSystemMXBean OS =
 46        (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
 47
 48    private static final MemoryMXBean MEM =
 49        ManagementFactory.getMemoryMXBean();
 50
 51    // tag::sendToAllSessionseMethod[]
 52    public static void sendToAllSessions(JsonObject systemLoad) {
 53        for (Session session : sessions) {
 54            try {
 55                session.getBasicRemote().sendObject(systemLoad);
 56            } catch (Exception e) {
 57                e.printStackTrace();
 58            }
 59        }
 60    }
 61    // end::sendToAllSessionseMethod[]
 62
 63    // tag::onOpenMethod[]
 64    // tag::onOpen[]
 65    @OnOpen
 66    // end::onOpen[]
 67    public void onOpen(Session session) {
 68        logger.info("Server connected to session: " + session.getId());
 69        sessions.add(session);
 70    }
 71    // end::onOpenMethod[]
 72
 73    // tag::onMessageMethod[]
 74    // tag::onMessage[]
 75    @OnMessage
 76    // end::onMessage[]
 77    public void onMessage(String option, Session session) {
 78        logger.info("Server received message \"" + option + "\" "
 79                    + "from session: " + session.getId());
 80        try {
 81            JsonObjectBuilder builder = Json.createObjectBuilder();
 82            builder.add("time", Calendar.getInstance().getTime().toString());
 83            // tag::cpuLoad[]
 84            if (option.equalsIgnoreCase("cpuLoad")
 85                || option.equalsIgnoreCase("both")) {
 86            // end::cpuLoad[]
 87                builder.add("cpuLoad", Double.valueOf(OS.getCpuLoad() * 100.0));
 88            }
 89            // tag::memoryUsageOrBoth[]
 90            if (option.equalsIgnoreCase("memoryUsage")
 91                || option.equalsIgnoreCase("both")) {
 92            // end::memoryUsageOrBoth[]
 93                long heapMax = MEM.getHeapMemoryUsage().getMax();
 94                long heapUsed = MEM.getHeapMemoryUsage().getUsed();
 95                builder.add("memoryUsage", Double.valueOf(heapUsed * 100.0 / heapMax));
 96            }
 97            // tag::sendToAllSessions[]
 98            JsonObject systemLoad = builder.build();
 99            sendToAllSessions(systemLoad);
100            // end::sendToAllSessions[]
101        } catch (Exception e) {
102            e.printStackTrace();
103        }
104    }
105    // end::onMessageMethod[]
106
107    // tag::onCloseMethod[]
108    // tag::onClose[]
109    @OnClose
110    // end::onClose[]
111    public void onClose(Session session, CloseReason closeReason) {
112        logger.info("Session " + session.getId()
113                    + " was closed with reason " + closeReason.getCloseCode());
114        sessions.remove(session);
115    }
116    // end::onCloseMethod[]
117
118    // tag::onError[]
119    @OnError
120    // end::onError[]
121    public void onError(Session session, Throwable throwable) {
122        logger.info("WebSocket error for " + session.getId() + " "
123                    + throwable.getMessage());
124    }
125}

Annotate the SystemService class with a @ServerEndpoint annotation to make it a WebSocket server. The @ServerEndpoint value attribute specifies the URI where the endpoint will be deployed. The encoders attribute specifies the classes to encode messages and the decoders attribute specifies the classes to decode messages. Provide methods that define the parts of the WebSocket lifecycle like establishing a connection, receiving a message, and closing the connection by annotating them with the @OnOpen, @OnMessage and @OnClose annotations respectively. The method that is annotated with the @OnError annotation is responsible for tackling errors.

The onOpen() method stores up the client sessions. The onClose() method displays the reason for closing the connection and removes the closing session from the client sessions.

The onMessage() method is called when receiving a message through the option parameter. The option parameter signifies which message to construct, either system load, memory usage data, or both, and sends out the JsonObject message. The sendToAllSessions() method uses the WebSocket API to broadcast the message to all client sessions.

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

SystemLoadEncoder.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 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.system;
13
14import jakarta.json.JsonObject;
15import jakarta.websocket.EncodeException;
16import jakarta.websocket.Encoder;
17
18// tag::SystemLoadEncoder[]
19public class SystemLoadEncoder implements Encoder.Text<JsonObject> {
20
21    @Override
22    // tag::encode[]
23    public String encode(JsonObject object) throws EncodeException {
24        return object.toString();
25    }
26    // end::encode[]
27}
28// end::SystemLoadEncoder[]

The SystemLoadEncoder class implements the Encoder.Text interface. Override the encode() method that accepts the JsonObject message and converts the message to a string.

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

SystemLoadDecoder.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022, 2023 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.system;
13
14import java.io.StringReader;
15
16import jakarta.json.Json;
17import jakarta.json.JsonObject;
18import jakarta.json.JsonReader;
19import jakarta.websocket.DecodeException;
20import jakarta.websocket.Decoder;
21
22// tag::SystemLoadDecoder[]
23public class SystemLoadDecoder implements Decoder.Text<JsonObject> {
24
25    @Override
26    // tag::decode[]
27    public JsonObject decode(String s) throws DecodeException {
28        try (JsonReader reader = Json.createReader(new StringReader(s))) {
29            return reader.readObject();
30        } catch (Exception e) {
31            JsonObject error = Json.createObjectBuilder()
32                    .add("error", e.getMessage())
33                    .build();
34            return error;
35        }
36    }
37    // end::decode[]
38
39    @Override
40    // tag::willDecode[]
41    public boolean willDecode(String s) {
42        try (JsonReader reader = Json.createReader(new StringReader(s))) {
43            reader.readObject();
44            return true;
45        } catch (Exception e) {
46            return false;
47        }
48    }
49    // end::willDecode[]
50
51}
52// end::SystemLoadDecoder[]

The SystemLoadDecoder class implements the Decoder.Text interface. Override the decode() method that accepts string message and decodes the string back into a JsonObject. The willDecode() override method checks out whether the string can be decoded into a JSON object and returns a Boolean value.

system/server.xml

 1<server description="system Service">
 2
 3    <featureManager>
 4        <!-- tag::webSocket[] -->
 5        <feature>websocket-2.1</feature>
 6        <!-- end::webSocket[] -->
 7        <!-- tag::jsonB[] -->
 8        <feature>jsonb-3.0</feature>
 9        <!-- end::jsonB[] -->
10    </featureManager>
11
12    <variable name="http.port" defaultValue="9081"/>
13    <variable name="https.port" defaultValue="9444"/>
14
15    <httpEndpoint host="*" httpPort="${http.port}"
16        httpsPort="${https.port}" id="defaultHttpEndpoint"/>
17
18    <webApplication location="guide-jakarta-websocket-system.war" contextRoot="/"/>
19
20    <logging consoleLogLevel="INFO"/>
21
22</server>

The required websocket and jsonb features for the system service have been enabled for you in the Liberty server.xml configuration file.

Creating the client service

In this section, you will create the WebSocket client that communicates with the WebSocket server and the scheduler that uses the WebSocket client to send messages to the server. You’ll also create an HTML file that uses a JavaScript WebSocket object to build a WebSocket connection, subscribe to different events, and display the broadcasting messages from the system service in a table.

On another command-line session, navigate to the start directory and run the following goal to start the client service in dev mode:

mvn -pl client liberty:dev

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

**************************************************
*     Liberty is running in dev mode.
Create the SystemClient class.
client/src/main/java/io/openliberty/guides/client/scheduler/SystemClient.java

client/SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022, 2023 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.client.scheduler;
13
14import java.io.IOException;
15import java.net.URI;
16import java.util.logging.Logger;
17
18import jakarta.websocket.ClientEndpoint;
19import jakarta.websocket.ContainerProvider;
20import jakarta.websocket.OnMessage;
21import jakarta.websocket.OnOpen;
22import jakarta.websocket.Session;
23import jakarta.websocket.WebSocketContainer;
24
25// tag::clientEndpoint[]
26@ClientEndpoint()
27// end::clientEndpoint[]
28public class SystemClient {
29
30    private static Logger logger = Logger.getLogger(SystemClient.class.getName());
31
32    private Session session;
33
34    // tag::constructor[]
35    public SystemClient(URI endpoint) {
36        try {
37            // tag::webSocketAPI[]
38            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
39            container.connectToServer(this, endpoint);
40            // end::webSocketAPI[]
41        } catch (Exception e) {
42            throw new RuntimeException(e);
43        }
44    }
45    // end::constructor[]
46
47    // tag::onOpen[]
48    @OnOpen
49    public void onOpen(Session session) {
50        this.session = session;
51        logger.info("Scheduler connected to the server.");
52    }
53    // end::onOpen[]
54
55    // tag::onMessage[]
56    @OnMessage
57    public void onMessage(String message, Session session) throws Exception {
58        logger.info("Scheduler received message from the server: " + message);
59    }
60    // end::onMessage[]
61
62    public void sendMessage(String message) {
63        session.getAsyncRemote().sendText(message);
64        logger.info("Scheduler sent message \"" + message + "\" to the server.");
65    }
66
67    public void close() {
68        try {
69            session.close();
70        } catch (IOException e) {
71            e.printStackTrace();
72        }
73        logger.info("Scheduler closed the session.");
74    }
75
76}

Annotate the SystemClient class with @ClientEndpoint annotation to make it as a WebSocket client. Create a constructor that uses the websocket APIs to establish connection with the server. Provide a method with the @OnOpen annotation that persists the client session when the connection is established. The onMessage() method that is annotated with the @OnMessage annotation handles messages from the server.

Create the SystemLoadScheduler class.
client/src/main/java/io/openliberty/guides/client/scheduler/SystemLoadScheduler.java

SystemLoadScheduler.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022, 2023 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.client.scheduler;
13
14import java.net.URI;
15import java.util.Random;
16
17import jakarta.annotation.PostConstruct;
18import jakarta.annotation.PreDestroy;
19import jakarta.ejb.Schedule;
20import jakarta.ejb.Singleton;
21
22@Singleton
23public class SystemLoadScheduler {
24
25    private SystemClient client;
26    // tag::messages[]
27    private static final String[] MESSAGES = new String[] {
28        "cpuLoad", "memoryUsage", "both" };
29    // end::messages[]
30
31    // tag::postConstruct[]
32    @PostConstruct
33    public void init() {
34        try {
35            // tag::systemClient[]
36            client = new SystemClient(new URI("ws://localhost:9081/systemLoad"));
37            // end::systemClient[]
38        } catch (Exception e) {
39            e.printStackTrace();
40        }
41    }
42    // end::postConstruct[]
43
44    // tag::schedule[]
45    @Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
46    // end::schedule[]
47    // tag::sendSystemLoad[]
48    public void sendSystemLoad() {
49        Random r = new Random();
50        client.sendMessage(MESSAGES[r.nextInt(MESSAGES.length)]);
51    }
52    // end::sendSystemLoad[]
53
54    @PreDestroy
55    public void close() {
56        client.close();
57    }
58}

The SystemLoadScheduler class uses the SystemClient class to establish a connection to the server by the ws://localhost:9081/systemLoad URI at the @PostConstruct annotated method. The sendSystemLoad() method calls the client to send a random string from either cpuLoad, memoryUsage, or both to the system service. Using the Jakarta Enterprise Beans Timer Service, annotate the sendSystemLoad() method with the @Schedule annotation so that it sends out a message every 10 seconds.

Now, create the front-end UI. The images and styles for the UI are provided for you.

Create the index.html file.
client/src/main/webapp/index.html

index.html

 1<!-- tag::copyright[] -->
 2<!--
 3  Copyright (c) 2022, 2024 IBM Corp.
 4
 5  Licensed under the Apache License, Version 2.0 (the "License");
 6  you may not use this file except in compliance with the License.
 7  You may obtain a copy of the License at
 8
 9    http://www.apache.org/licenses/LICENSE-2.0
10
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16-->
17<!-- end::copyright[] -->
18<!DOCTYPE html>
19<html>
20    <head>
21        <meta charset="UTF-8">
22        <title>Open Liberty - Jakarta WebSocket Example</title>
23        <link rel="stylesheet" href="css/styles.css">
24        <link href="favicon.ico" rel="icon" />
25        <link href="favicon.ico" rel="shortcut icon" />
26    </head>
27    <body>
28        <section id="appIntro">
29            <div id="titleSection">
30                <h1 id="appTitle">Jakarta WebSocket Example</h1>
31                <div class="line"></div>
32                <div class="headerImage"></div>
33            </div>
34
35            <div class="msSection" id="systemLoads">
36                <div class="headerRow">
37                    <div class="headerIcon">
38                      <img src="img/sysProps.svg"/>
39                    </div>
40                    <div class="headerTitle" id="sysPropTitle">
41                      <h2>System Loads</h2>
42                    </div>
43                </div>
44                <div class="sectionContent">
45                    <table id="systemLoadsTable">
46                        <tbody id="systemLoadsTableBody">
47                            <tr>
48                                <th>Time</th>
49                                <th>CPU Load (%)</th>
50                                <th>Heap Memory Usage (%)</th>
51                            </tr>
52                        </tbody>
53                    </table>
54                </div>
55            </div>
56        </section>
57        <footer class="bodyFooter">
58            <div class="bodyFooterLink">
59                <a id="licenseLink"
60                   href="https://github.com/OpenLiberty/open-liberty/blob/release/LICENSE"
61                >License</a>
62                <a href="https://github.com/OpenLiberty">GitHub</a>
63                <a href="https://stackoverflow.com/questions/tagged/open-liberty">stackoverflow</a>
64                <a href="https://groups.io/g/openliberty">groups.io</a>
65                <a href="https://openliberty.io/">openliberty.io</a>
66            </div>
67            <p id="footer_text">an IBM open source project</p>
68            <p id="footer_copyright">&copy;Copyright IBM Corp. 2022, 2024</p>
69        </footer>
70        <script>
71    const webSocket = new WebSocket('ws://localhost:9081/systemLoad')
72
73    webSocket.onopen = function (event) {
74        console.log(event);
75    };
76
77    webSocket.onmessage = function (event) {
78        var data = JSON.parse(event.data);
79        var tableRow = document.createElement('tr');
80        var cpuLoad = data.cpuLoad == null ? '-' : data.cpuLoad.toFixed(7);
81        var memoryUsage = data.memoryUsage == null ? '-' : data.memoryUsage.toFixed(2);
82        tableRow.innerHTML = '<td>' + data.time + '</td>' +
83                             '<td>' + cpuLoad + '</td>' +
84                             '<td>' + memoryUsage + '</td>';
85        document.getElementById('systemLoadsTableBody').appendChild(tableRow);
86    };
87    
88    webSocket.onerror = function (event) {
89        console.log(event);
90    };
91        </script>
92    </body>
93</html>

The index.html front-end UI displays a table in which each row contains a time, system load, and the memory usage of the system service. Use a JavaScript WebSocket object to establish a connection to the server by the ws://localhost:9081/systemLoad URI. The webSocket.onopen event is triggered when the connection is established. The webSocket.onmessage event receives messages from the server and inserts a row with the data from the message into the table. The webSocket.onerror event defines how to tackle errors.

client/server.xml

 1<server description="client service">
 2
 3    <featureManager>
 4        <!-- tag::features[] -->
 5        <feature>restfulWS-3.1</feature>
 6        <feature>websocket-2.1</feature>
 7        <feature>enterpriseBeansLite-4.0</feature>
 8        <!-- end::features[] -->
 9    </featureManager>
10
11    <variable name="http.port" defaultValue="9080"/>
12    <variable name="https.port" defaultValue="9443"/>
13
14    <httpEndpoint
15        host="*"
16        httpPort="${http.port}"
17        httpsPort="${https.port}"
18        id="defaultHttpEndpoint"
19    />
20
21    <webApplication location="guide-jakarta-websocket-client.war" contextRoot="/"/>
22
23    <logging consoleLogLevel="INFO"/>
24
25</server>

The required features for the client service are enabled for you in the Liberty server.xml configuration file.

Running the application

Because you are running the system and client services in dev mode, the changes that you made are automatically picked up. You’re now ready to check out your application in your browser.

Point your browser to the http://localhost:9080 URL to test out the client service. Notice that the table is updated every 10 seconds.

Visit the http://localhost:9080 URL again on a different tab or browser and verify that both sessions are updated every 10 seconds.

Testing the application

Create the SystemClient class.
system/src/test/java/it/io/openliberty/guides/system/SystemClient.java

system/test/SystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022, 2023 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 it.io.openliberty.guides.system;
13
14import java.net.URI;
15
16import io.openliberty.guides.system.SystemLoadDecoder;
17import jakarta.json.JsonObject;
18import jakarta.websocket.ClientEndpoint;
19import jakarta.websocket.ContainerProvider;
20import jakarta.websocket.OnMessage;
21import jakarta.websocket.OnOpen;
22import jakarta.websocket.Session;
23import jakarta.websocket.WebSocketContainer;
24
25@ClientEndpoint()
26public class SystemClient {
27
28    private Session session;
29
30    public SystemClient(URI endpoint) {
31        try {
32            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
33            container.connectToServer(this, endpoint);
34        } catch (Exception e) {
35            throw new RuntimeException(e);
36        }
37    }
38
39    @OnOpen
40    public void onOpen(Session session) {
41        this.session = session;
42    }
43
44    // tag::onMessage[]
45    @OnMessage
46    public void onMessage(String message, Session userSession) throws Exception {
47        SystemLoadDecoder decoder = new SystemLoadDecoder();
48        JsonObject systemLoad = decoder.decode(message);
49        SystemServiceIT.verify(systemLoad);
50    }
51    // end::onMessage[]
52
53    public void sendMessage(String message) {
54        session.getAsyncRemote().sendText(message);
55    }
56
57    public void close() throws Exception {
58        session.close();
59    }
60
61}

The SystemClient class is used to communicate and test the system service. Its implementation is similar to the client class from the client service that you created in the previous section. At the onMessage() method, decode and verify the message.

Create the SystemServiceIT class.
system/src/test/java/it/io/openliberty/guides/system/SystemServiceIT.java

SystemServiceIT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2022, 2023 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 it.io.openliberty.guides.system;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15import static org.junit.jupiter.api.Assertions.assertNotNull;
16import static org.junit.jupiter.api.Assertions.assertTrue;
17
18import java.net.URI;
19import java.util.concurrent.CountDownLatch;
20import java.util.concurrent.TimeUnit;
21
22import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
23import org.junit.jupiter.api.Order;
24import org.junit.jupiter.api.Test;
25import org.junit.jupiter.api.TestMethodOrder;
26
27import jakarta.json.JsonObject;
28
29@TestMethodOrder(OrderAnnotation.class)
30public class SystemServiceIT {
31
32    private static CountDownLatch countDown;
33
34    // tag::testSystem[]
35    @Test
36    @Order(1)
37    public void testSystem() throws Exception {
38        startCountDown(1);
39        URI uri = new URI("ws://localhost:9081/systemLoad");
40        SystemClient client = new SystemClient(uri);
41        client.sendMessage("both");
42        countDown.await(5, TimeUnit.SECONDS);
43        client.close();
44        assertEquals(0, countDown.getCount(),
45                "The countDown was not 0.");
46    }
47    // end::testSystem[]
48
49    // tag::testSystemMultipleSessions[]
50    @Test
51    @Order(2)
52    public void testSystemMultipleSessions() throws Exception {
53        startCountDown(3);
54        URI uri = new URI("ws://localhost:9081/systemLoad");
55        SystemClient client1 = new SystemClient(uri);
56        SystemClient client2 = new SystemClient(uri);
57        SystemClient client3 = new SystemClient(uri);
58        client2.sendMessage("cpuLoad");
59        countDown.await(5, TimeUnit.SECONDS);
60        client1.close();
61        client2.close();
62        client3.close();
63        assertEquals(0, countDown.getCount(),
64            "The countDown was not 0.");
65    }
66    // end::testSystemMultipleSessions[]
67
68    private static void startCountDown(int count) {
69        countDown = new CountDownLatch(count);
70    }
71
72    public static void verify(JsonObject systemLoad) {
73        assertNotNull(systemLoad.getString("time"));
74        assertTrue(
75            systemLoad.getJsonNumber("cpuLoad") != null
76            || systemLoad.getJsonNumber("memoryUsage") != null
77        );
78        countDown.countDown();
79    }
80}

There are two test cases to ensure correct functionality of the system service. The testSystem() method verifies one client connection and the testSystemMultipleSessions() method verifies multiple client connections.

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 the system service.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.system.SystemServiceIT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.247 s - in it.io.openliberty.guides.system.SystemServiceIT

Results:

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

When you are done checking out the services, exit dev mode by pressing CTRL+C in the command-line sessions where you ran the system and client services.

Great work! You’re done!

You developed an application that subscribes to real time updates by using Jakarta WebSocket and Open Liberty.

Guide Attribution

Bidirectional communication between services using Jakarta WebSocket 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