Of course! Here is a comprehensive guide to using WebSockets in Java, covering the two main approaches:
- Java-EE (Jakarta EE) / Jakarta WebSocket: The standard, built-in API for Java applications (like those running on Tomcat, WildFly, etc.).
- Spring WebSocket: A more feature-rich and integration-friendly approach within the Spring ecosystem.
What are WebSockets?
WebSockets provide a full-duplex, persistent communication channel between a client (usually a web browser) and a server. Unlike HTTP, which is request-response-based, the WebSocket connection is opened once and allows both the client and server to send messages to each other at any time without needing the other to ask first.
Key Use Cases:
- Real-time chat applications
- Live notifications
- Multiplayer online games
- Live data dashboards (stock prices, sports scores)
- Collaborative editing tools (like Google Docs)
Java-EE / Jakarta WebSocket (The Standard Approach)
This is the standard API defined by the Java Community Process. It's a good choice if you're not using the Spring framework or want to stick to the standard specification.
Step 1: Project Setup
You need a Java EE application server that supports WebSockets, such as Apache Tomcat (version 8+), WildFly, or Payara.
If you're using Maven, you don't need to add any extra dependencies for the API itself, as it's provided by the server. However, you'll need the API for annotation processing.
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version> <!-- Or 9.1.0 for Java EE 8 -->
<scope>provided</scope>
</dependency>
If you're using Gradle:
implementation 'jakarta.platform:jakarta.jakartaee-api:10.0.0' // provided by the server
Step 2: Create the WebSocket Endpoint
This is the server-side component that handles WebSocket connections. An endpoint is a Java class annotated with @ServerEndpoint.
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
// This annotation marks this class as a WebSocket endpoint.
// The value is the URL path where clients will connect.
@ServerEndpoint("/chat")
public class ChatEndpoint {
// A thread-safe Set to store all active client sessions.
private static final Set<Session> chatroomUsers = Collections.synchronizedSet(new HashSet<>());
// This method is called when a new WebSocket connection is established.
@OnOpen
public void onOpen(Session session) {
chatroomUsers.add(session);
System.out.println("New connection opened. Session ID: " + session.getId());
broadcast("User " + session.getId() + " has joined the chat.");
}
// This method is called when a message is received from a client.
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("Message from " + session.getId() + ": " + message);
// Broadcast the received message to all connected clients.
broadcast("User " + session.getId() + ": " + message);
}
// This method is called when a WebSocket connection is closed.
@OnClose
public void onClose(Session session) {
chatroomUsers.remove(session);
System.out.println("Connection closed. Session ID: " + session.getId());
broadcast("User " + session.getId() + " has left the chat.");
}
// This method is called if an error occurs during the WebSocket communication.
@OnError
public void onError(Throwable error, Session session) {
System.err.println("Error on session " + session.getId());
error.printStackTrace();
}
// Helper method to send a message to all connected clients.
private void broadcast(String message) {
chatroomUsers.forEach(session -> {
try {
// getBasicRemote() is for synchronous sending.
// getAsyncRemote() is for asynchronous sending and is generally preferred.
session.getBasicRemote().sendText(message);
} catch (IOException e) {
System.err.println("Error broadcasting message: " + e.getMessage());
// If sending fails, the client might be disconnected, so remove it.
chatroomUsers.remove(session);
}
});
}
}
Step 3: Deploy and Test
- Package your application as a
.warfile. - Deploy it to your Tomcat server (e.g., by dropping the
.warfile into thewebappsdirectory). - The WebSocket endpoint will be available at
ws://localhost:8080/your-app-name/chat.
You can test this using simple HTML/JavaScript.
Client-Side HTML (index.html)
<!DOCTYPE html>
<html>
<head>Java WebSocket Chat</title>
<style>
#messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
#input { width: 80%; padding: 5px; }
#send { width: 18%; padding: 5px; }
</style>
</head>
<body>
<h1>Java WebSocket Chat</h1>
<div id="messages"></div>
<input type="text" id="input" placeholder="Type a message...">
<button id="send">Send</button>
<script>
// Connect to the WebSocket endpoint
const socket = new WebSocket('ws://localhost:8080/your-app-name/chat');
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('input');
const sendButton = document.getElementById('send');
// Called when the connection is established
socket.onopen = function(event) {
console.log('Connection established!');
messagesDiv.innerHTML += '<p style="color: green;">Connected to the chat server.</p>';
};
// Called when a message is received from the server
socket.onmessage = function(event) {
console.log('Message received from server:', event.data);
const messageElement = document.createElement('p');
messageElement.textContent = event.data;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight; // Auto-scroll
};
// Called when the connection is closed
socket.onclose = function(event) {
console.log('Connection closed.');
messagesDiv.innerHTML += '<p style="color: red;">Disconnected from the chat server.</p>';
};
// Called when an error occurs
socket.onerror = function(error) {
console.error('WebSocket Error:', error);
messagesDiv.innerHTML += '<p style="color: red;">An error occurred.</p>';
};
// Send a message when the send button is clicked
sendButton.onclick = function() {
const message = input.value.trim();
if (message) {
socket.send(message);
input.value = '';
}
};
// Also send message when Enter key is pressed
input.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendButton.click();
}
});
</script>
</body>
</html>
Spring WebSocket (The Integrated Approach)
Spring provides a more powerful and flexible way to integrate WebSockets into your application. It simplifies configuration and offers features like message broadcasting using the SimpMessagingTemplate.
Step 1: Project Setup
You need to add the necessary Spring dependencies.
<!-- pom.xml -->
<dependencies>
<!-- For Spring WebSocket support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>6.1.0</version> <!-- Use the latest Spring version -->
</dependency>
<!-- For STOMP, a subprotocol for messaging -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>6.1.0</version>
</dependency>
<!-- For a simple embedded server (like Tomcat) to run the app -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>3.2.0</version> <!-- Use the latest Spring Boot version -->
</dependency>
</dependencies>
Step 2: Enable WebSocket Support
Create a configuration class to enable WebSocket and STOMP messaging.
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker // This annotation enables WebSocket message handling
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// Enables a simple in-memory message broker for "/topic" destinations.
// Messages sent to destinations starting with "/topic" will be broadcast to all subscribers.
config.enableSimpleBroker("/topic");
// Sets the prefix for messages that are annotated with @MessageMapping.
// Messages sent from controllers to this prefix will be routed.
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Registers the "/chat" endpoint, enabling SockJS fallback options.
// SockJS is useful for browsers that don't support WebSockets natively.
registry.addEndpoint("/chat").withSockJS();
}
}
Step 3: Create the Controller
This controller will handle incoming messages and send outgoing broadcasts.
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
// This annotation marks this class as a Spring MVC controller.
@Controller
public class ChatController {
// This method is called when a message is sent to the "/app/chat" destination.
// @MessageMapping maps the method to a destination.
@MessageMapping("/chat")
// @SendTo specifies that the return value of this method should be sent to
// the "/topic/messages" destination, where all clients are subscribed.
@SendTo("/topic/messages")
public ChatMessage sendMessage(ChatMessage message) {
// You can add business logic here, like saving to a database.
System.out.println("Received message: " + message.getContent());
return message; // The returned message is broadcast to all subscribers.
}
}
// A simple POJO to represent a chat message.
class ChatMessage {
private String sender;
private String content;
// Getters and Setters are required for JSON serialization/deserialization.
public String getSender() { return sender; }
public void setSender(String sender) { this.sender = sender; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
Step 4: Create the Client-Side HTML
For Spring STOMP, the client-side JavaScript is slightly different. We use the stomp.js library to simplify the STOMP protocol handling.
<!DOCTYPE html>
<html>
<head>Spring WebSocket Chat</title>
<style>
#messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
#input { width: 80%; padding: 5px; }
#send { width: 18%; padding: 5px; }
</style>
<!-- Include SockJS and Stomp.js libraries -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
<h1>Spring WebSocket Chat</h1>
<div id="messages"></div>
<input type="text" id="input" placeholder="Type a message...">
<button id="send">Send</button>
<script>
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('input');
const sendButton = document.getElementById('send');
// 1. Connect to the SockJS endpoint
const socket = new SockJS('http://localhost:8080/chat');
// 2. Initialize the STOMP client over the SockJS connection
const stompClient = Stomp.over(socket);
// 3. Connect to the STOMP server
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
messagesDiv.innerHTML += '<p style="color: green;">Connected to the chat server.</p>';
// 4. Subscribe to the topic where messages will be broadcast
stompClient.subscribe('/topic/messages', function (message) {
console.log('Received message: ', message.body);
const chatMessage = JSON.parse(message.body);
const messageElement = document.createElement('p');
messageElement.textContent = `[${chatMessage.sender}]: ${chatMessage.content}`;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});
});
// 5. Send a message when the send button is clicked
sendButton.onclick = function() {
const content = input.value.trim();
if (content) {
// Construct the message object
const chatMessage = {
sender: 'User1', // You can get this from a login system
content: content
};
// Send the message to the "/app/chat" destination on the server
stompClient.send("/app/chat", {}, JSON.stringify(chatMessage));
input.value = '';
}
};
// Also send message when Enter key is pressed
input.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendButton.click();
}
});
</script>
</body>
</html>
Comparison: Jakarta WebSocket vs. Spring WebSocket
| Feature | Jakarta WebSocket (Standard) | Spring WebSocket |
|---|---|---|
| Simplicity | Very simple for basic point-to-point or broadcast scenarios. | More setup initially, but much more powerful for complex messaging. |
| Configuration | Annotation-based on the endpoint class. | Requires a WebSocketMessageBrokerConfigurer class. |
| Messaging Model | Direct onMessage handling. Manual session management. |
Leverages the STOMP subprotocol for a more structured messaging model (topics, queues). |
| Broadcasting | Manual, requires iterating over Session objects. |
Easy and efficient with @SendTo and SimpMessagingTemplate. |
| Integration | Works in any Java EE container. | Tightly integrated with the Spring ecosystem (Security, MVC, etc.). |
| Client Library | Uses native browser WebSocket API. |
Works with native API but is often used with stomp.js and sockjs-client for better compatibility and features. |
| Best For | Simple applications, quick prototypes, or when avoiding a framework. | Complex applications, enterprise systems, and projects already using the Spring framework. |
When to use which?
- Use Jakarta WebSocket if you need a lightweight, standard solution without adding the Spring framework to your project. It's perfect for simple chat or notification features.
- Use Spring WebSocket if you are building a large-scale application with Spring, need advanced features like STOMP for message routing, or want seamless integration with other parts of the Spring ecosystem like Spring Security.
