
Introduction
Modern applications demand real-time capabilities. Users expect instant updates for notifications, chat messages, live dashboards, order tracking, and collaborative features. Traditional HTTP request-response patterns cannot deliver this experience efficiently because clients must repeatedly poll the server for changes. WebSocket technology solves this problem by establishing persistent, bidirectional connections between clients and servers. In the Java ecosystem, Spring Boot provides excellent support for WebSocket communication through the STOMP protocol. This guide walks you through building a complete real-time notification system using Spring Boot and WebSocket, covering configuration, broadcasting, user-targeted messages, security considerations, and production best practices.
What is WebSocket?
WebSocket is a full-duplex communication protocol that operates over a single, long-lived TCP connection. Unlike HTTP, where every interaction requires the client to initiate a new request, WebSocket allows both the client and server to send data at any time. Once the initial handshake completes, the connection stays open, eliminating the overhead of repeated connection establishment.
This architecture makes WebSocket ideal for use cases that require low latency and frequent updates:
- Real-time notifications and alerts
- Live chat applications
- Collaborative editing tools
- Stock tickers and price updates
- Multiplayer games
- Live dashboards and monitoring
The WebSocket protocol starts with an HTTP upgrade request. If the server supports WebSocket, it responds with a 101 status code, and the connection switches from HTTP to WebSocket. From that point forward, both parties can send messages freely without additional HTTP overhead.
Why Spring Boot for WebSocket?
Spring Boot simplifies WebSocket development significantly. The framework provides built-in support for the STOMP (Simple Text Oriented Messaging Protocol) messaging protocol, which adds a layer of abstraction over raw WebSocket. STOMP introduces concepts like destinations, subscriptions, and message routing that make building complex messaging systems easier.
Key benefits of using Spring Boot for WebSocket include:
- Declarative configuration with annotations
- Built-in message broker for pub/sub patterns
- Integration with Spring Security for authentication
- Support for SockJS fallback for older browsers
- Scalability options with external message brokers like RabbitMQ
Project Setup
Start by adding the WebSocket dependency to your project. If you use Maven, add this to your pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
For Gradle projects, add this line to your build.gradle:
implementation 'org.springframework.boot:spring-boot-starter-websocket'
This dependency includes everything needed for STOMP-based messaging over WebSocket, including the SockJS library for browser compatibility.
Configuring WebSocket
Create a configuration class that enables the WebSocket message broker and defines endpoints:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-notifications")
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
}
This configuration establishes several important settings. The endpoint /ws-notifications is where clients connect to establish WebSocket connections. The withSockJS() method enables fallback options for browsers that do not support native WebSocket. The message broker configuration defines three destination prefixes:
- /topic – Used for broadcasting messages to multiple subscribers
- /queue – Used for point-to-point messaging
- /app – Prefix for messages sent from clients to server-side handlers
- /user – Prefix for user-specific destinations
Creating the Notification Model
Define a simple model class to represent notifications:
public class NotificationMessage {
private String id;
private String title;
private String content;
private String type;
private LocalDateTime timestamp;
public NotificationMessage() {
this.id = UUID.randomUUID().toString();
this.timestamp = LocalDateTime.now();
}
public NotificationMessage(String title, String content, String type) {
this();
this.title = title;
this.content = content;
this.type = type;
}
// Getters and setters omitted for brevity
}
The type field allows categorizing notifications as info, warning, error, or success, which helps clients display them appropriately.
Building the Notification Controller
Create a controller that handles incoming messages and broadcasts them to subscribers:
@Controller
public class NotificationController {
@MessageMapping("/notify")
@SendTo("/topic/notifications")
public NotificationMessage broadcastNotification(NotificationMessage message) {
return message;
}
@MessageMapping("/notify.admin")
@SendTo("/topic/admin-notifications")
public NotificationMessage broadcastAdminNotification(NotificationMessage message) {
message.setType("admin");
return message;
}
}
The @MessageMapping annotation works similarly to @RequestMapping but for WebSocket messages. When a client sends a message to /app/notify, Spring routes it to the broadcastNotification method. The @SendTo annotation specifies where to send the return value, in this case to all subscribers of /topic/notifications.
Sending Notifications Programmatically
Often you need to send notifications from anywhere in your application, not just in response to client messages. Use SimpMessagingTemplate for this purpose:
@Service
public class NotificationService {
private final SimpMessagingTemplate messagingTemplate;
public NotificationService(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
public void broadcastNotification(NotificationMessage notification) {
messagingTemplate.convertAndSend("/topic/notifications", notification);
}
public void sendToUser(String username, NotificationMessage notification) {
messagingTemplate.convertAndSendToUser(
username,
"/queue/notifications",
notification
);
}
public void notifyOrderUpdate(String username, String orderId, String status) {
NotificationMessage notification = new NotificationMessage(
"Order Update",
"Your order " + orderId + " is now " + status,
"info"
);
sendToUser(username, notification);
}
}
This service can be injected anywhere in your application. The convertAndSendToUser method sends messages to specific users, which requires authentication to be configured properly.
Client-Side Implementation
Clients connect using the SockJS and STOMP libraries. Here is a complete JavaScript implementation:
const socket = new SockJS('/ws-notifications');
const stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
// Subscribe to broadcast notifications
stompClient.subscribe('/topic/notifications', function(message) {
const notification = JSON.parse(message.body);
displayNotification(notification);
});
// Subscribe to user-specific notifications
stompClient.subscribe('/user/queue/notifications', function(message) {
const notification = JSON.parse(message.body);
displayNotification(notification);
});
});
function sendNotification(title, content) {
stompClient.send('/app/notify', {}, JSON.stringify({
title: title,
content: content,
type: 'info'
}));
}
function displayNotification(notification) {
console.log('Received:', notification.title, notification.content);
// Update UI with notification
}
The client establishes a connection, subscribes to relevant topics, and can both send and receive messages. User-specific subscriptions use the /user prefix, which Spring automatically resolves to the authenticated user.
Securing WebSocket Connections
Production applications must secure WebSocket connections. Spring Security integrates seamlessly with WebSocket. First, configure security for the WebSocket endpoints:
@Configuration
@EnableWebSocketSecurity
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/app/**").authenticated()
.simpSubscribeDestMatchers("/topic/**").authenticated()
.simpSubscribeDestMatchers("/user/**").authenticated()
.anyMessage().denyAll();
}
@Override
protected boolean sameOriginDisabled() {
return true;
}
}
This configuration requires authentication for all messaging operations. For JWT-based authentication, you can pass the token during the STOMP connection handshake and validate it using a custom interceptor.
Handling Connection Events
Track when users connect and disconnect to manage presence or clean up resources:
@Component
public class WebSocketEventListener {
private static final Logger logger = LoggerFactory.getLogger(WebSocketEventListener.class);
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
logger.info("New WebSocket connection established");
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get("username");
if (username != null) {
logger.info("User disconnected: " + username);
}
}
}
Scaling with External Message Brokers
The simple in-memory broker works for single-instance applications but does not scale horizontally. For production deployments with multiple application instances, use an external message broker like RabbitMQ:
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
With an external broker, messages are distributed across all connected application instances, ensuring users receive notifications regardless of which instance they connect to.
Error Handling and Resilience
Implement proper error handling to manage failed message deliveries:
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
On the client side, implement reconnection logic to handle network interruptions gracefully. SockJS provides automatic reconnection, but you should also handle subscription restoration after reconnecting.
Best Practices
Follow these guidelines for production-ready WebSocket implementations:
- Always authenticate WebSocket connections
- Validate and sanitize all incoming messages
- Implement rate limiting to prevent abuse
- Use heartbeats to detect stale connections
- Monitor connection counts and message throughput
- Set appropriate message size limits
- Use external brokers for horizontal scaling
- Implement graceful degradation for clients without WebSocket support
Conclusion
Building real-time notification systems with Spring Boot and WebSocket creates responsive, engaging user experiences. The combination of STOMP messaging, Spring’s annotation-driven configuration, and robust security integration makes it straightforward to implement sophisticated real-time features. Start with the simple broker for development, then migrate to an external broker like RabbitMQ for production scalability. Whether you are building a chat application, live dashboard, or notification system, this architecture provides the foundation for reliable real-time communication.
For securing your Spring Boot APIs, read Spring Boot + JWT: Secure Your REST API. To explore resilience patterns for distributed systems, see Circuit Breakers and Resilience Patterns in Microservices. For async processing patterns, check out Spring Boot Async Processing. You can also refer to the official Spring WebSocket documentation for additional configuration options.