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:
- @EnableWebSocketMessageBrokerenables WebSocket message handling backed by a message broker
- configureMessageBroker()configures the message broker:- /topicis a common prefix for topics that clients can subscribe to
- /appis the prefix for messages that should be routed to- @MessageMappingmethods
 
- registerStompEndpoints()registers the- /wsendpoint, 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:8080in 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
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!