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 [email protected]. I’d greatly appreciate your feedback! :)