Spring STOMP
Introduction
Real-time communication has become an essential part of modern web applications. Whether you're building a chat application, a collaborative tool, or a dashboard with live updates, the ability to push data from the server to clients instantaneously can significantly enhance user experience.
Spring provides excellent support for real-time messaging through WebSockets, and STOMP (Simple Text Oriented Messaging Protocol) adds a structured messaging layer on top of WebSockets. In this guide, we'll explore how to implement real-time communication in Spring applications using STOMP over WebSockets.
What is STOMP?
STOMP (Simple Text Oriented Messaging Protocol) is a simple text-based messaging protocol that works over various transport protocols, including WebSockets. It provides a frame-based format for sending messages between clients and servers in a reliable and interoperable way.
Key advantages of using STOMP with Spring:
- Standardized Protocol: STOMP is widely supported across various programming languages and platforms.
- Message Routing: STOMP provides a destination-based messaging model that simplifies routing messages.
- Header Support: STOMP messages can include headers for metadata.
- Built-in Integration: Spring has excellent support for STOMP through the spring-messaging module.
Setting Up Spring STOMP
Step 1: Add Dependencies
First, add the necessary dependencies to your Spring Boot project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.4</version>
</dependency>
Step 2: Configure WebSocket and STOMP
Create a WebSocket configuration class:
package com.example.messagingapp.config;
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 configureMessageBroker(MessageBrokerRegistry config) {
// Enable a simple in-memory message broker
// Messages with destinations prefixed with /topic will be routed to the broker
config.enableSimpleBroker("/topic");
// Set prefix for messages bound for methods annotated with @MessageMapping
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Register STOMP endpoints
registry.addEndpoint("/ws")
.withSockJS(); // Add SockJS fallback option
}
}
Let's break down the configuration:
@EnableWebSocketMessageBroker
enables WebSocket message handling backed by a message brokerconfigureMessageBroker()
configures the message broker:/topic
is a common prefix for topics that clients can subscribe to/app
is the prefix for messages that should be routed to@MessageMapping
methods
registerStompEndpoints()
registers the/ws
endpoint, allowing WebSocket connection
Step 3: Create Message Models
Let's create message models for our example chat application:
package com.example.messagingapp.model;
public class ChatMessage {
private String content;
private String sender;
// Default constructor (required for JSON conversion)
public ChatMessage() {
}
public ChatMessage(String content, String sender) {
this.content = content;
this.sender = sender;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
}
Step 4: Create a Controller
Now, create a controller to handle WebSocket messages:
package com.example.messagingapp.controller;
import com.example.messagingapp.model.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class ChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(ChatMessage chatMessage) {
return new ChatMessage(chatMessage.getSender() + " joined the chat!", "System");
}
}
In this controller:
@MessageMapping("/chat.sendMessage")
handles messages sent to/app/chat.sendMessage
@SendTo("/topic/public")
broadcasts the returned message to all clients subscribed to/topic/public
Client-Side Implementation
Let's implement a simple HTML and JavaScript client that uses STOMP over WebSockets:
<!DOCTYPE html>
<html>
<head>
<title>Spring Boot WebSocket Chat</title>
<script src="/webjars/sockjs-client/1.5.1/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.4/stomp.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style>
#chat-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
}
#message-area {
height: 300px;
overflow-y: auto;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #eee;
}
.message {
padding: 5px;
margin-bottom: 5px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<div id="chat-container">
<h1>Spring Boot WebSocket Chat</h1>
<div id="connect-container">
<input type="text" id="name" placeholder="Your Name" />
<button id="connect">Connect</button>
<button id="disconnect" disabled="disabled">Disconnect</button>
</div>
<div id="chat-content" style="display:none">
<div id="message-area"></div>
<div>
<input type="text" id="message-input" placeholder="Type a message..." />
<button id="send">Send</button>
</div>
</div>
</div>
<script>
var stompClient = null;
var username = null;
function connect() {
username = $("#name").val().trim();
if (username) {
$("#connect-container").hide();
$("#chat-content").show();
var socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
// Subscribe to the public topic
stompClient.subscribe('/topic/public', function(message) {
showMessage(JSON.parse(message.body));
});
// Tell everyone you joined
stompClient.send("/app/chat.addUser",
{},
JSON.stringify({sender: username, content: ''})
);
$("#connect").attr("disabled", "disabled");
$("#disconnect").removeAttr("disabled");
});
}
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
$("#connect-container").show();
$("#chat-content").hide();
$("#connect").removeAttr("disabled");
$("#disconnect").attr("disabled", "disabled");
console.log("Disconnected");
}
function sendMessage() {
var messageContent = $("#message-input").val().trim();
if (messageContent && stompClient) {
var chatMessage = {
sender: username,
content: messageContent
};
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
$("#message-input").val('');
}
}
function showMessage(message) {
$("#message-area").append("<div class='message'><b>" + message.sender + ":</b> " + message.content + "</div>");
$("#message-area").scrollTop($("#message-area")[0].scrollHeight);
}
$(function() {
$("#connect").click(function() { connect(); });
$("#disconnect").click(function() { disconnect(); });
$("#send").click(function() { sendMessage(); });
// Send message on enter key
$("#message-input").keypress(function(e) {
if (e.which === 13) {
sendMessage();
}
});
});
</script>
</body>
</html>
Save this file as index.html
in your src/main/resources/static
directory.
Running and Testing the Application
- Start your Spring Boot application
- Open
http://localhost:8080
in multiple browser windows - Enter different names in each window and connect
- Start sending messages - you should see the messages appear in all connected windows
Example Output
When a user named "John" connects, all clients would see:
System: John joined the chat!
When John sends a message "Hello everyone!", all clients would see:
John: Hello everyone!
Advanced STOMP Features
User-Specific Messages
Spring STOMP supports sending messages to specific users. First, enable user destinations in your config:
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
Then, you can send messages to specific users:
@Controller
public class ChatController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageMapping("/private-message")
public void handlePrivateMessage(PrivateMessage message, Principal principal) {
messagingTemplate.convertAndSendToUser(
message.getRecipient(),
"/queue/reply",
new PrivateMessage(
message.getMessage(),
principal.getName(),
message.getRecipient()
)
);
}
}
Clients would subscribe to their user-specific destination:
stompClient.subscribe('/user/queue/reply', function(message) {
// Handle private messages
});
Message Broker Integration
For production applications, you might want to use an external message broker. Spring STOMP integrates with RabbitMQ and ActiveMQ:
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// Connect to external RabbitMQ broker
registry.enableStompBrokerRelay("/topic", "/queue")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
registry.setApplicationDestinationPrefixes("/app");
}
This configuration connects to an external STOMP broker for greater scalability.
Real-World Use Cases
Real-Time Dashboards
STOMP can be used to create real-time dashboards that update automatically:
@Scheduled(fixedRate = 5000)
public void sendMetricUpdates() {
SystemMetrics metrics = metricsService.getCurrentMetrics();
messagingTemplate.convertAndSend("/topic/metrics", metrics);
}
Collaborative Editing
Applications like Google Docs use real-time messaging to enable collaborative editing:
@MessageMapping("/document.update")
@SendTo("/topic/document/{documentId}")
public DocumentUpdate updateDocument(@DestinationVariable String documentId,
DocumentUpdate update) {
documentService.applyUpdate(documentId, update);
return update;
}
Notifications
Notify users about important events in the system:
public void notifyUser(String username, Notification notification) {
messagingTemplate.convertAndSendToUser(
username,
"/queue/notifications",
notification
);
}
Summary
Spring STOMP provides a powerful and flexible way to implement real-time messaging in your applications. In this guide, we've covered:
- Setting up STOMP over WebSockets in a Spring Boot application
- Creating controllers to handle message routing
- Implementing a simple chat client
- Advanced features like user-specific messaging and external broker integration
- Real-world use cases for STOMP messaging
By leveraging Spring's STOMP support, you can easily add real-time capabilities to your applications, enhancing user experience with instantaneous updates and interactive features.
Additional Resources
Exercises
- Enhance the chat application to show who's currently online
- Implement private messaging between users
- Add support for different chat rooms/channels
- Integrate with an external message broker like RabbitMQ
- Create a real-time collaborative drawing application using STOMP
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)