Bidirectional communication between services using Jakarta WebSocket
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.
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
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
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
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
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
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 , , or 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
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
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
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
Prerequisites:
Nice work! Where to next?
What did you think of this guide?
Thank you for your feedback!
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