STOMP over WebSocket — a higher-level messaging protocol that works over WebSocket and supports topics, queues, and subscriptions.
Simple analogy: WebSocket is like a wire (low-level). STOMP is like a telephone protocol (subscriber → number → conversation). You subscribe to a "channel" and receive messages. Think of it as a walkie-talkie system: WebSocket is the physical radio, STOMP is the protocol that lets you say "I want to listen to Channel 5" (subscribe) and "I'm sending a message to Channel 3" (send). Without STOMP, you would have to build the routing and subscription logic yourself.
Why it matters: Raw WebSocket gives you a bidirectional pipe but no built-in concepts of topics, queues, or message routing. STOMP adds this layer, making it trivial to build chat rooms, live dashboards, collaborative editing, and real-time notifications without reinventing message routing.
Configuration:
1@Configuration2@EnableWebSocketMessageBroker3public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {45 @Override6 public void configureMessageBroker(MessageBrokerRegistry config) {7 config.enableSimpleBroker("/topic", "/queue"); // Message broker8 config.setApplicationDestinationPrefixes("/app"); // Application prefix9 config.setUserDestinationPrefix("/user"); // Per-user queue prefix10 }1112 @Override13 public void registerStompEndpoints(StompEndpointRegistry registry) {14 registry.addEndpoint("/ws")15 .setAllowedOrigins("*")16 .withSockJS(); // Fallback for older browsers17 }18}
Controllers:
1@Controller2public class ChatController {34 // Client sends to /app/chat.send5 @MessageMapping("/chat.send")6 @SendTo("/topic/messages") // Broadcasts to all subscribers7 public Message sendMessage(Message message) {8 return message;9 }1011 // Private messages (to single user)12 @MessageMapping("/chat.private")13 @SendToUser("/queue/messages")14 public Message sendPrivate(Message message, Principal principal) {15 message.setSender(principal.getName());16 return message;17 }1819 // Sending to a specific user programmatically20 @MessageMapping("/chat.notification")21 public void sendNotification(@Payload NotificationEvent event,22 SimpMessageHeaderAccessor headerAccessor) {23 String userId = event.getTargetUserId();24 messagingTemplate.convertAndSendToUser(25 userId, "/queue/notifications", event26 );27 }28}
Client (STOMP.js):
1const stompClient = new StompJs.Client({2 brokerURL: "ws://localhost:8080/ws",3 onConnect: () => {4 stompClient.subscribe("/topic/messages", (message) => {5 console.log(JSON.parse(message.body));6 });78 stompClient.subscribe("/user/queue/messages", (message) => {9 console.log("Private:", JSON.parse(message.body));10 });11 },12 onDisconnect: () => console.log("Disconnected"),13 reconnectDelay: 500014});15stompClient.activate();1617// Sending18stompClient.publish({19 destination: "/app/chat.send",20 body: JSON.stringify({sender: "Alice", content: "Hello!"})21});
Production considerations:
SimpleBroker is in-memory only. For multi-instance deployments, use RabbitMQ or external STOMP broker.ChannelInterceptor to validate STOMP headers before messages reach controllers.setHandshakeHandler() to handle connection timeouts and clean up stale sessions.spring.websocket.max-text-message-size and max-binary-message-size to prevent abuse.