Introduction
In today’s digital landscape, real-time communication is essential for creating engaging and interactive web applications. Whether it’s live chat features, instant notifications, or collaborative tools, users expect seamless, event-driven experiences without constant page refreshes. Traditional HTTP protocols fall short for these scenarios because they are request-response based and unidirectional.
This is where WebSocket comes into play. WebSocket enables full-duplex communication between the client and server over a single TCP connection, allowing data to flow in both directions simultaneously. When integrated with Spring Boot, it becomes a powerful tool for building scalable, real-time applications.
In this article, we’ll explore WebSocket fundamentals, its communication protocol, the STOMP messaging protocol, and a step-by-step guide to creating a simple real-time chat application using Spring Boot and WebSocket.
What is a WebSocket?
WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. Unlike HTTP, which closes the connection after each request-response cycle, WebSocket keeps the connection open, enabling persistent, bidirectional data exchange.
This makes WebSocket ideal for applications requiring low-latency updates, such as:
- Live sports scores
- Stock market tickers
- Online gaming
- Collaborative editing tools
- Chat applications
WebSocket starts with an HTTP handshake to upgrade the connection, after which both parties can send messages independently.
WebSocket Communication Protocol
The WebSocket protocol offers several key advantages:
- Bi-directional Communication: Both client and server can initiate data transfer at any time.
- Full-Duplex: Simultaneous sending and receiving of data.
- Single TCP Connection: Reduces overhead compared to multiple HTTP requests.
- Lightweight: Minimal protocol overhead after the initial handshake.
The protocol uses opcodes for different frame types (e.g., text, binary, ping/pong) and supports masking for security.
What is STOMP?
STOMP (Simple Text-Oriented Messaging Protocol) is a messaging protocol that defines a format for structured communication. It operates over TCP or WebSocket and provides an interoperable wire format for messaging between clients and brokers.
Key features of STOMP:
- Commands: Includes CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE, etc.
- Headers and Body: Messages consist of a command, headers (key-value pairs), and an optional body.
- Broker Support: Can use in-memory brokers (like Spring’s SimpleBroker) or external ones like RabbitMQ or ActiveMQ.
In Spring Boot, STOMP over WebSocket simplifies message routing, subscriptions, and broadcasting.
Creating the Spring Boot WebSocket Application
Let’s build a simple chat application where users can join, send messages, and leave the chat room in real-time.
Prerequisites
- Java 17 or later
- Gradle 7.5+ or Maven 3.5+
- IDE like IntelliJ IDEA or Eclipse
Setting Up a Java Spring Boot Project
- Create a new Spring Boot project using Spring Initializr with Maven.
- Add the following dependency to your pom.xml for WebSocket support:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- Ensure your application.properties or application.yml is configured if needed (defaults are often sufficient).
Define the Domain Class ‘ChatMessage’
Create a ChatMessage class to represent messages:
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChatMessage {
private MessageType type;
private String content;
private String sender;
}
Define the Enum ‘MessageType’
Create an enum for message types:
public enum MessageType {
CHAT, JOIN, LEAVE
}
Define the WebSocket Configuration Class
Configure WebSocket and STOMP in WebSocketConfig:
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
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(“/ws”).withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes(“/app”);
registry.enableSimpleBroker(“/topic”);
}
}
- /ws: WebSocket endpoint.
- /app: Prefix for application-handled messages.
- /topic: Prefix for broadcast messages.
Define the WebSocket Listener Class
Handle connection events in WebSocketEventListener:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
@Component
@Slf4j
@RequiredArgsConstructor
public class WebSocketEventListener {
private final SimpMessageSendingOperations messagingTemplate;
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
log.info(“Received a new web socket connection”);
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get(“username”);
if (username != null) {
log.info(“User Disconnected: ” + username);
ChatMessage chatMessage = ChatMessage.builder()
.type(MessageType.LEAVE)
.sender(username)
.build();
messagingTemplate.convertAndSend(“/topic/public”, chatMessage);
}
}
}
This listens for connect/disconnect events and broadcasts leave messages.
Define the Controller Class
Handle messages in ChatController:
The WebSocket configuration routes all messages from clients with the prefix “/app” to appropriate message handling methods annotated with @MessageMapping. For example, a message with destination /app/addUser will be routed to the addUser() method, and a message with destination /app/sendMessage will be routed to the sendMessage() method.
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
@Controller
public class ChatController {
@MessageMapping(“/chat.sendMessage”)
@SendTo(“/topic/public”)
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping(“/chat.addUser”)
@SendTo(“/topic/public”)
public ChatMessage addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put(“username”, chatMessage.getSender());
return chatMessage;
}
}
- /chat.sendMessage: Broadcasts chat messages.
- /chat.addUser: Adds user and broadcasts join message.
Define the Index.html
Create a simple HTML page for the UI (src/main/resources/static/index.html):
<!DOCTYPE html>
<html>
<head>
<title>Chat Application</title>
<link href=”https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css” rel=”stylesheet”>
</head>
<body>
<div class=”container”>
<br/>
<div class=”row”>
<div class=”col-md-6 offset-md-3″>
<h3>Chat Room</h3>
<form id=”loginForm”>
<div class=”form-group”>
<input type=”text” id=”username” class=”form-control” placeholder=”Your Name” required>
</div>
<br/>
<button type=”submit” class=”btn btn-primary”>Join Chat</button>
</form>
<div id=”chatPage” style=”display:none;”>
<ul id=”messageArea” class=”list-group”></ul>
<form id=”messageForm”>
<div class=”form-group”>
<input type=”text” id=”message” class=”form-control” placeholder=”Type a message…” required>
</div>
<br/>
<button type=”submit” class=”btn btn-primary”>Send</button>
</form>
</div>
</div>
</div>
</div>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js”></script>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js”></script>
<script src=”/js/main.js”></script>
</body>
</html>
This above HTML file defines the UI for the chat application and includes the SockJS and STOMP JavaScript libraries. SockJS is a browser JavaScript library that provides a WebSocket-like object. It gives you a coherent, cross-browser Javascript API that creates a low-latency, full-duplex, cross-domain communication channel between the browser and the web server. STOMP JS is the stomp client for JavaScript.
Define the JavaScript File
Create main.js in src/main/resources/static/js/ for client-side logic:
The stompClient.subscribe() function takes a callback method called whenever a message arrives on the subscribed topic. The connect() function uses the SockJS and STOMP client to establish a connection to the /ws endpoint configured in the Spring Boot application. The client subscribes to the /topic/public destination.
var stompClient = null;
function setConnected(connected) {
document.getElementById(‘loginForm’).style.display = connected ? ‘none’ : ‘block’;
document.getElementById(‘chatPage’).style.display = connected ? ‘block’ : ‘none’;
}
function connect(event) {
event.preventDefault();
var username = document.getElementById(‘username’).value.trim();
if (username) {
var socket = new SockJS(‘/ws’);
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log(‘Connected: ‘ + frame);
stompClient.subscribe(‘/topic/public’, function (message) {
showMessage(JSON.parse(message.body));
});
stompClient.send(“/app/chat.addUser”, {}, JSON.stringify({sender: username, type: ‘JOIN’}));
});
}
}
function sendMessage(event) {
event.preventDefault();
var messageContent = document.getElementById(‘message’).value.trim();
if (messageContent && stompClient) {
var chatMessage = {
sender: document.getElementById(‘username’).value,
content: messageContent,
type: ‘CHAT’
};
stompClient.send(“/app/chat.sendMessage”, {}, JSON.stringify(chatMessage));
document.getElementById(‘message’).value = ”;
}
}
function showMessage(message) {
var messageElement = document.createElement(‘li’);
messageElement.classList.add(‘list-group-item’);
if (message.type === ‘JOIN’) {
messageElement.innerHTML = message.sender + ‘ joined!’;
} else if (message.type === ‘LEAVE’) {
messageElement.innerHTML = message.sender + ‘ left!’;
} else {
messageElement.innerHTML = message.sender + ‘: ‘ + message.content;
}
document.getElementById(‘messageArea’).appendChild(messageElement);
}
document.getElementById(‘loginForm’).addEventListener(‘submit’, connect);
document.getElementById(‘messageForm’).addEventListener(‘submit’, sendMessage);
window.onbeforeunload = function() {
if (stompClient !== null) {
stompClient.disconnect();
}
};
Running the Application
- Start the Spring Boot application.
- Open http://localhost:8080 in multiple browser tabs.
- Enter usernames and join the chat.
- Send messages and observe real-time updates across tabs.
Conclusion
By integrating WebSocket with Spring Boot and STOMP, you can build efficient, real-time bidirectional applications without the overhead of constant polling. This chat application demonstrates the basics, but you can extend it with features like private messaging, authentication, or integration with external message brokers.