Jakarta WebSocket Essentials: A Guide to Full-Duplex Communication in Java
Do you want to explore real-time apps in Jakarta EE? This article covers the basics of building a real-time chat app using Jakarta WebSocket and Open Liberty.
Join the DZone community and get the full member experience.
Join For FreeHave you ever wondered what happens when you send a message to friends or family over the Internet? It’s not just magic — there’s a fascinating technology at work behind the scenes called WebSocket. This powerful protocol enables real-time communication, allowing messages to flow seamlessly between users.
Join us as we dive deeper into the world of WebSocket! We’ll explore how this technology operates and even create a simple application together to see it in action. Get ready to unlock the potential of real-time communication!
What Is a WebSocket?
WebSocket is a communication protocol that provides full-duplex communication channels over a single, long-lived connection, which means we can transfer data in both directions simultaneously. Unlike traditional HTTP requests, where a client sends a request to a server and waits for a response, WebSocket allows both the client and server to send and receive messages independently and concurrently. This is achieved through a persistent connection that remains open for real-time data exchange.
For this blog, we are going to use Jakarta, the enterprise edition of Java, to implement WebSocket. Before we dive deeper into WebSocket, let's take a look at Jakarta EE.
What Is Jakarta EE?
Jakarta EE (formerly Java EE) is a set of specifications that extend the Java SE (Standard Edition) with a collection of APIs for building enterprise-grade applications. It provides a robust framework for developing scalable, reliable, and secure applications that can run on various servers and cloud platforms.
WebSockets in Jakarta
WebSockets in Jakarta EE offer a powerful and efficient way to enable real-time communication between clients and servers. Jakarta EE's WebSocket API simplifies the process of building real-time applications by providing a robust framework for managing WebSocket connections. With its event-driven model and seamless integration with other Jakarta EE technologies, developers can create interactive, responsive applications that enhance user engagement and experience.
WebSocket Protocol
Let's jump back to WebSocket and learn about the WebSocket protocol. The WebSocket protocol is designed to provide full-duplex communication channels over a single TCP connection, making it ideal for real-time applications. Here are the key aspects and fundamentals of how the WebSocket protocol works:
1. Establishing a Connection
Handshake Process
The connection starts with a handshake initiated by the client through an HTTP request. This request includes specific headers indicating that the client wishes to establish a WebSocket connection.
Server Response
If the server supports WebSocket, it responds with a status code 101 (Switching Protocols) and confirms the upgrade.
2. Data Framing
Messages
After the connection is established, data is transmitted in the form of messages. Each message can be text (UTF-8) or binary data.
Frames
WebSocket messages are divided into frames. Each frame contains a header that includes information like whether the frame is a final frame or if it’s part of a larger message, as well as the payload length and masking information.
3. Message Types
- Text frames: These frames contain UTF-8 encoded text messages.
- Binary frames: Used for binary data, such as images or files
- Control frames: These include frames for closing the connection and ping/pong frames for keep-alive functionality.
4. Closing the Connection
Close Frame
Either the client or server can initiate the closing of the WebSocket connection by sending a close frame, which includes a status code and an optional reason for the closure.
Acknowledgment
Upon receiving a close frame, the other party must respond with its own close frame to confirm the closure.
5. Keep-Alive Mechanism
Ping/pong frames: To maintain an active connection and check if the other party is still connected, WebSockets can use ping/pong frames. The server sends a ping frame, and the client responds with a pong frame, helping to keep the connection alive.
6. Security Considerations
Secure WebSockets (WSS): Similar to HTTPS for HTTP, WebSocket connections can be secured using the WSS protocol, which encrypts the data transmitted over the connection using TLS.
Setting Up Jakarta WebSocket
To better understand WebSocket, let's build a small app that implements WebSocket. In this project, we are going to use Open Liberty, which is an open-source Java application server designed to support enterprise Java applications developed by IBM.
Setting up Jakarta WebSocket on Open Liberty is straightforward, as Open Liberty supports Jakarta EE APIs, including Jakarta WebSocket.
1. Install JDK 11 or above.
2. Set the JAVA_HOME
environment variable. For example:
- Linux/macOS:
export JAVA_HOME=/path/to/jdk
- Windows:
set JAVA_HOME=C:\path\to\jdk
3. Download Open Liberty:
- Go to the Open Liberty starter page.
- Choose the appropriate release and download the .zip file.
4. Extract the files:
- Extract the downloaded .zip file to your preferred directory.
5. Update server.xml file in liberty/config
with the WebSocket configuration:
<featureManager>
<feature>jakartaee-10.0</feature>
<feature>microProfile-6.1</feature>
<feature>webProfile-10.0</feature>
<feature>websocket-2.1</feature>
</featureManager>
6. Create the WebSocket Java Class:
- In
src/main/java/com/openLibertyWebsocket/rest
, create a class namedChatEndpoint.java
:
package com.openLibertyWebsocket.rest;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ServerEndpoint("/chat")
public class ChatEndpoint {
// Maintain a set of all active WebSocket sessions
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("Connected: " + session.getId());
}
@OnMessage
public void onMessage(String message, Session senderSession) {
System.out.println("Received: " + message + " from " + senderSession.getId());
// Broadcast the message to all connected clients
synchronized (sessions) {
for (Session session : sessions) {
if (session.isOpen()) {
try {
session.getBasicRemote().sendText("User " + senderSession.getId() + ": " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
System.out.println("Disconnected: " + session.getId());
}
}
This Java class, ChatEndpoint
, defines a WebSocket server endpoint for a simple chat application using the Jakarta WebSocket API. This allows clients to connect to the /chat
endpoint, send messages, and broadcast messages to all other connected clients.
- Class Annotation
@ServerEndpoint
:@ServerEndpoint("/chat")
indicates that this class is a WebSocket endpoint at the URL /chat. When a WebSocket client connects tows://<server-address>/chat
, it interacts with this endpoint.
- Session management with
Set<Session>
:sessions
: A static, synchronized Set of Session objects, representing all active WebSocket connections. TheCollections.synchronizedSet(new HashSet<>())
ensures thread-safe access, allowing concurrent modification when clients connect or disconnect.
- Methods for WebSocket Events: Each WebSocket lifecycle event has a corresponding method in this class, annotated appropriately to handle client connections, messages, and disconnections.
@OnOpen
: TheonOpen
method is called when a client establishes a connection.- Adds the client’s Session to the sessions set, allowing the server to track active connections.
- Prints a message to the console confirming the client’s connection with its unique session ID.
@OnMessage
: TheonMessage
method is called whenever a connected client sends a message to the server.- Receives the message as a String and the Session of the sender (
senderSession
). - Logs the received message and the sender’s session ID for debugging.
- Broadcasting: Uses a synchronized loop to send the message to all active clients in the sessions set:
- If the session is open, the
sendText
method sends the message to that client. - Adds a prefix (e.g.,
User <session-id>
) to identify the sender in the message broadcast to all clients.
- If the session is open, the
- Receives the message as a String and the Session of the sender (
@OnClose
: TheonClose
method is called when a client disconnects.- Removes the client’s Session from the sessions set.
- Logs a disconnect message with the session ID, helpful for tracking connections and debugging.
The entire backend code is available in this repository.
7. Run the project using Maven: mvn liberty:dev
8. Test the chat application.
- Open two browser windows.
- Use the browser console as the WebSocket client (in Chrome, press F12 > Console tab).
- Connect each tab to the WebSocket server by running the following JavaScript.
const ws = new WebSocket("ws://localhost:9080/ws-blog/chat");
ws.onopen = () => console.log("Connected to chat");
ws.onmessage = (msg) => console.log("Message from server: " + msg.data);
ws.onclose = () => console.log("Disconnected from chat");
// Send a message to the chat
function sendMessage(text) {
ws.send(text);
}
// Example: To send a message, type sendMessage("Hello from user!");
9. Start chatting:
- In each console, use
sendMessage("Your message")
to send messages. - You should see messages from both users appearing in both browser consoles, creating a live chat experience.
10. After testing the WebSocket application, we can proceed to create a chat user interface using React.js. Here is the sample code that we have used to create one.
import React, { useEffect, useState, useRef } from 'react';
const Chat = () => {
const ws = useRef(null); // Use useRef to persist WebSocket instance
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [username, setUsername] = useState('');
const [isUsernameSet, setIsUsernameSet] = useState(false);
useEffect(() => {
// Initialize WebSocket connection only once
ws.current = new WebSocket('ws://localhost:9080/ws-blog/chat');
// Handle incoming messages
ws.current.onmessage = (event) => {
const newMessage = event.data;
// Remove random prefix if present
const displayMessage = newMessage.replace(/User\s[\w-]+:\s/, '');
setMessages((prevMessages) => [...prevMessages, displayMessage]);
};
// Clean up WebSocket on component unmount
return () => {
if (ws.current) {
ws.current.close();
}
};
}, []);
// Function to send a message with the specified username
const sendMessage = () => {
if (ws.current && ws.current.readyState === WebSocket.OPEN && input) {
const messageToSend = `${username}: ${input}`; // Use specified username
ws.current.send(messageToSend);
setInput(''); // Clear the input field
}
};
// Set the specified username
const handleSetUsername = () => {
if (username.trim()) {
setIsUsernameSet(true);
}
};
return (
<div>
<h1>WebSocket Chat</h1>
{!isUsernameSet ? (
// Username input screen
<div>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter your username"
/>
<button onClick={handleSetUsername}>Start Chat</button>
</div>
) : (
// Chat interface
<div>
<div style={{ border: '1px solid #ccc', height: '300px', overflowY: 'scroll', marginBottom: '10px' }}>
{messages.map((msg, index) => (
<div key={index}>{msg}</div>
))}
</div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
)}
</div>
);
};
export default Chat;
React Using WebSockets
This code defines a basic chat interface in React using WebSockets for real-time communication. The Chat component allows a user to set a username, send messages to a WebSocket server, and receive/display incoming messages.
1. WebSocket Reference (ws)
ws
is defined with useRef(null)
to maintain a persistent reference to the WebSocket instance across renders.
2. useEffect Hook for WebSocket Connection
useEffect
initializes the WebSocket connection to ws://localhost:9080/ws-blog/chat
when the component mounts. This is only run once due to the empty dependency array ([]
).
- Message handling: When a message is received, it triggers the
onmessage
event, where:newMessage
captures the received message text.displayMessage
uses a regex to strip any unwanted random user prefix (in the formatUser <random-identifier>:
), displaying only the intended message content.setMessages
updates the messages array with the new message.
- Cleanup: When the component unmounts, the WebSocket connection is closed to free up resources.
3. sendMessage Function
- Called when the user clicks the "Send" button
- Checks if the WebSocket connection is open and the input field has text
- Formats the message with the username and input content, then sends it to the server
- Clears the input field
4. handleSetUsername Function
- Called when the user sets a username and clicks "Start Chat"
- If a non-empty username is entered,
isUsernameSet
is set totrue
, hiding the username input and displaying the chat interface.
5. UI Rendering
- If
isUsernameSet
isfalse
, the component displays an input to enter a username and a "Start Chat" button. - Once the username is set, the main chat interface is shown:
- Messages display: A scrollable div shows each message in the messages array.
- Message input: An input field and "Send" button allow users to type and send messages.
The entire frontend code is available in this repository.
Conclusion
As we wrap up, you've now unlocked the basics of WebSockets and learned how to create a simple WebSocket application with Open Liberty. Here’s a quick recap of what we covered:
1. Understanding WebSockets
We explored the WebSocket protocol, which enables real-time, bidirectional communication between clients and servers. Unlike traditional HTTP requests, WebSockets maintain a persistent connection that allows data to flow freely in both directions.
2. Setting Up Open Liberty
You learned how to set up your Open Liberty environment and create a basic Jakarta EE application that uses WebSockets. We covered the necessary steps to prepare your project structure and configure the server.
3. Creating a WebSocket Endpoint
We walked through the creation of a WebSocket endpoint using the @ServerEndpoint
annotation. This included writing the server-side logic to handle client connections, messages, and disconnections.
4. Building the Client Application
You gained insights into how to build a simple client application that connects to your WebSocket server. We discussed the JavaScript code necessary to establish a connection, send messages, and receive updates in real time.
By mastering these fundamental concepts, you now have the tools to build interactive and dynamic applications that can engage users in real-time. WebSockets are a powerful feature of modern web development, and with your new knowledge, you can start creating applications that leverage their capabilities.
Opinions expressed by DZone contributors are their own.
Comments