Skip to main content

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:

xml
<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:

java
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 broker
  • configureMessageBroker() 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:

java
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:

java
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:

html
<!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​

  1. Start your Spring Boot application
  2. Open http://localhost:8080 in multiple browser windows
  3. Enter different names in each window and connect
  4. 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:

java
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}

Then, you can send messages to specific users:

java
@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:

javascript
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:

java
@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:

java
@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:

java
@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:

java
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​

  1. Enhance the chat application to show who's currently online
  2. Implement private messaging between users
  3. Add support for different chat rooms/channels
  4. Integrate with an external message broker like RabbitMQ
  5. Create a real-time collaborative drawing application using STOMP


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)