Handler support provides base classes, decorators, and utilities for building robust WebSocket handlers. This includes convenience base classes for text and binary handlers, decorators for exception handling and logging, and thread-safe session wrappers.
Convenient base class that implements WebSocketHandler with empty method implementations and type-specific message handling methods.
/**
* Abstract base class for WebSocketHandler implementations. Provides empty
* implementations of lifecycle methods and routes messages to type-specific
* handler methods.
*/
public abstract class AbstractWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// Empty implementation
}
@Override
public final void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception {
if (message instanceof TextMessage) {
handleTextMessage(session, (TextMessage) message);
} else if (message instanceof BinaryMessage) {
handleBinaryMessage(session, (BinaryMessage) message);
} else if (message instanceof PongMessage) {
handlePongMessage(session, (PongMessage) message);
} else {
throw new IllegalStateException("Unexpected WebSocket message type: " + message);
}
}
/**
* Handle incoming text messages. Default implementation is empty (no-op).
* Subclasses should override to provide actual handling.
*/
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
// Empty implementation
}
/**
* Handle incoming binary messages. Default implementation is empty (no-op).
* Subclasses should override to provide actual handling.
*/
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message)
throws Exception {
// Empty implementation
}
/**
* Handle incoming pong messages. Default implementation is empty.
*/
protected void handlePongMessage(WebSocketSession session, PongMessage message)
throws Exception {
// Empty implementation
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception)
throws Exception {
// Empty implementation
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
throws Exception {
// Empty implementation
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}Handler that only accepts text messages and rejects binary messages.
/**
* Convenient base class for WebSocketHandler implementations that only
* handle text messages. Binary messages are rejected with
* CloseStatus.NOT_ACCEPTABLE.
*/
public class TextWebSocketHandler extends AbstractWebSocketHandler {
@Override
protected final void handleBinaryMessage(WebSocketSession session,
BinaryMessage message) {
try {
session.close(CloseStatus.NOT_ACCEPTABLE.withReason(
"Binary messages not supported"));
} catch (IOException ex) {
// Ignore
}
}
}Handler that only accepts binary messages and rejects text messages.
/**
* Convenient base class for WebSocketHandler implementations that only
* handle binary messages. Text messages are rejected with
* CloseStatus.NOT_ACCEPTABLE.
*/
public class BinaryWebSocketHandler extends AbstractWebSocketHandler {
@Override
protected final void handleTextMessage(WebSocketSession session,
TextMessage message) {
try {
session.close(CloseStatus.NOT_ACCEPTABLE.withReason(
"Text messages not supported"));
} catch (IOException ex) {
// Ignore
}
}
}Wraps another WebSocketHandler to add behavior. Can be chained to compose multiple decorators.
/**
* Wraps another WebSocketHandler to add additional behavior.
* Decorators can be chained together.
*/
public class WebSocketHandlerDecorator implements WebSocketHandler {
/**
* Create a new decorator for the given handler.
*
* @param delegate the handler to wrap
*/
public WebSocketHandlerDecorator(WebSocketHandler delegate);
/**
* Return the wrapped handler.
*/
public WebSocketHandler getDelegate();
/**
* Return the last handler in the decorator chain (the actual handler).
*/
public WebSocketHandler getLastHandler();
/**
* Unwrap the given handler to find the last handler in the chain.
*
* @param handler the handler (possibly decorated)
* @return the last handler in the chain
*/
public static WebSocketHandler unwrap(WebSocketHandler handler);
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception;
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception;
@Override
public void handleTransportError(WebSocketSession session, Throwable exception)
throws Exception;
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
throws Exception;
@Override
public boolean supportsPartialMessages();
@Override
public List<String> getSubProtocols();
}Factory interface for creating handler decorators. Used to apply decorators to handlers during configuration.
/**
* Factory for creating WebSocketHandler decorators. Allows for applying
* decorators to handlers during WebSocket configuration.
*/
public interface WebSocketHandlerDecoratorFactory {
/**
* Decorate the given WebSocketHandler.
*
* @param handler the handler to decorate
* @return the decorated handler
*/
WebSocketHandler decorate(WebSocketHandler handler);
}Decorator that catches exceptions and closes the session with CloseStatus.SERVER_ERROR.
/**
* WebSocketHandler decorator that catches exceptions from the delegate
* handler and closes the session with CloseStatus.SERVER_ERROR.
*/
public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
/**
* Create a new exception handling decorator.
*
* @param delegate the handler to wrap
*/
public ExceptionWebSocketHandlerDecorator(WebSocketHandler delegate);
/**
* Utility method to close a session with SERVER_ERROR status after
* an exception occurs.
*
* @param session the session to close
* @param exception the exception that occurred
* @param logger the logger to use for error messages
*/
public static void tryCloseWithError(
WebSocketSession session,
Throwable exception,
Log logger);
}Decorator that adds logging to all WebSocket lifecycle events.
/**
* WebSocketHandler decorator that logs all handler invocations at
* trace level, including arguments and results.
*/
public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator {
/**
* Create a new logging decorator.
*
* @param delegate the handler to wrap
*/
public LoggingWebSocketHandlerDecorator(WebSocketHandler delegate);
}Wraps another WebSocketSession to add behavior. Can be chained to compose multiple decorators.
/**
* Wraps another WebSocketSession to add additional behavior.
* Decorators can be chained together.
*/
public class WebSocketSessionDecorator implements WebSocketSession {
/**
* Create a new decorator for the given session.
*
* @param delegate the session to wrap
*/
public WebSocketSessionDecorator(WebSocketSession delegate);
/**
* Return the wrapped session.
*/
public WebSocketSession getDelegate();
/**
* Return the last session in the decorator chain (the actual session).
*/
public WebSocketSession getLastSession();
/**
* Unwrap the given session to find the last session in the chain.
*
* @param session the session (possibly decorated)
* @return the last session in the chain
*/
public static WebSocketSession unwrap(WebSocketSession session);
// All WebSocketSession methods delegate to the wrapped session
@Override
public String getId();
@Override
public URI getUri();
@Override
public HttpHeaders getHandshakeHeaders();
@Override
public Map<String, Object> getAttributes();
@Override
public Principal getPrincipal();
@Override
public InetSocketAddress getLocalAddress();
@Override
public InetSocketAddress getRemoteAddress();
@Override
public String getAcceptedProtocol();
@Override
public void setTextMessageSizeLimit(int messageSizeLimit);
@Override
public int getTextMessageSizeLimit();
@Override
public void setBinaryMessageSizeLimit(int messageSizeLimit);
@Override
public int getBinaryMessageSizeLimit();
@Override
public List<WebSocketExtension> getExtensions();
@Override
public void sendMessage(WebSocketMessage<?> message) throws IOException;
@Override
public boolean isOpen();
@Override
public void close() throws IOException;
@Override
public void close(CloseStatus status) throws IOException;
}Thread-safe session decorator that manages concurrent message sending with configurable limits and overflow strategies.
/**
* Wraps a WebSocketSession to provide thread-safe message sending with
* configurable send time limits, buffer size limits, and overflow handling.
* Prevents blocking indefinitely when sending messages and handles backpressure.
*/
public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorator {
/**
* Create a decorator with send limits.
*
* @param delegate the session to wrap
* @param sendTimeLimit the maximum time (ms) to wait for a send operation
* @param bufferSizeLimit the maximum buffer size in bytes
*/
public ConcurrentWebSocketSessionDecorator(
WebSocketSession delegate,
int sendTimeLimit,
int bufferSizeLimit);
/**
* Create a decorator with send limits and overflow strategy.
*
* @param delegate the session to wrap
* @param sendTimeLimit the maximum time (ms) to wait for a send operation
* @param bufferSizeLimit the maximum buffer size in bytes
* @param overflowStrategy the strategy for handling buffer overflow
*/
public ConcurrentWebSocketSessionDecorator(
WebSocketSession delegate,
int sendTimeLimit,
int bufferSizeLimit,
OverflowStrategy overflowStrategy);
/**
* Return the send time limit in milliseconds.
*/
public int getSendTimeLimit();
/**
* Return the buffer size limit in bytes.
*/
public int getBufferSizeLimit();
/**
* Return the overflow strategy.
*/
public OverflowStrategy getOverflowStrategy();
/**
* Return the current buffer size in bytes.
*/
public int getBufferSize();
/**
* Return the time in milliseconds since the current send operation started,
* or 0 if no send is in progress.
*/
public long getTimeSinceSendStarted();
/**
* Set a callback to invoke when a message cannot be sent due to overflow.
* The callback receives the message that could not be sent.
*
* @param messageCallback the callback to invoke
*/
public void setMessageCallback(Consumer<WebSocketMessage<?>> messageCallback);
/**
* Strategy for handling buffer overflow when the send buffer is full.
*/
public enum OverflowStrategy {
/**
* Terminate the session when buffer overflow occurs.
*/
TERMINATE,
/**
* Drop the message when buffer overflow occurs.
*/
DROP
}
@Override
public void sendMessage(WebSocketMessage<?> message) throws IOException;
}Creates a new handler instance for each WebSocket connection. Useful when handlers maintain per-connection state.
/**
* WebSocketHandler implementation that instantiates a new handler of the
* specified type for each WebSocket connection. Useful when handlers maintain
* per-connection state that should not be shared across connections.
*/
public class PerConnectionWebSocketHandler implements WebSocketHandler, BeanFactoryAware {
/**
* Create a per-connection handler factory.
*
* @param handlerType the handler class to instantiate per connection
*/
public PerConnectionWebSocketHandler(Class<? extends WebSocketHandler> handlerType);
/**
* Create a per-connection handler factory with partial message support flag.
*
* @param handlerType the handler class to instantiate
* @param supportsPartialMessages whether to support partial messages
*/
public PerConnectionWebSocketHandler(
Class<? extends WebSocketHandler> handlerType,
boolean supportsPartialMessages);
/**
* Set the BeanFactory to use for instantiating handler instances.
* Called automatically by Spring when registered as a bean.
*
* @param beanFactory the BeanFactory to use
*/
public void setBeanFactory(BeanFactory beanFactory);
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception;
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception;
@Override
public void handleTransportError(WebSocketSession session, Throwable exception)
throws Exception;
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
throws Exception;
@Override
public boolean supportsPartialMessages();
}Instantiates handlers using Spring's BeanFactory, allowing for dependency injection and lifecycle management.
/**
* Provider that instantiates handlers using Spring's BeanFactory.
* Allows for dependency injection into handler instances.
*
* @param <T> the handler type
*/
public class BeanCreatingHandlerProvider<T> {
/**
* Create a provider for the given handler type.
*
* @param handlerType the handler class
*/
public BeanCreatingHandlerProvider(Class<? extends T> handlerType);
/**
* Return the handler type.
*/
public Class<? extends T> getHandlerType();
/**
* Get a new handler instance from the BeanFactory.
*/
public T getHandler();
/**
* Destroy the given handler instance if it implements DisposableBean
* or has a custom destroy method.
*
* @param handler the handler to destroy
*/
public void destroy(T handler);
}Exception thrown when session limits are exceeded (e.g., send buffer overflow).
/**
* Exception thrown when session limits are exceeded, such as buffer
* size limits or send time limits in ConcurrentWebSocketSessionDecorator.
*/
public class SessionLimitExceededException extends IllegalStateException {
/**
* Create an exception with a message and close status.
*
* @param message the error message
* @param status the close status to use when closing the session
*/
public SessionLimitExceededException(String message, CloseStatus status);
/**
* Return the close status associated with this exception.
*/
public CloseStatus getStatus();
}import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class ChatHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
String payload = message.getPayload();
System.out.println("Received: " + payload);
// Send response
session.sendMessage(new TextMessage("Processed: " + payload));
}
}import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class ThreadSafeHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// Wrap session for thread-safe sending
// 5 second send timeout, 64KB buffer limit, TERMINATE on overflow
WebSocketSession safeSession = new ConcurrentWebSocketSessionDecorator(
session,
5000, // 5 second send timeout
64 * 1024 // 64KB buffer limit
);
// Store the decorated session
session.getAttributes().put("safeSession", safeSession);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
WebSocketSession safeSession =
(WebSocketSession) session.getAttributes().get("safeSession");
// Can safely send from multiple threads
safeSession.sendMessage(new TextMessage("Echo: " + message.getPayload()));
}
}import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator.OverflowStrategy;
public class DroppingHandler extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
ConcurrentWebSocketSessionDecorator safeSession =
new ConcurrentWebSocketSessionDecorator(
session,
5000,
64 * 1024,
OverflowStrategy.DROP // Drop messages on overflow
);
// Set callback for dropped messages
safeSession.setMessageCallback(droppedMessage -> {
System.err.println("Message dropped for session " + session.getId());
});
session.getAttributes().put("safeSession", safeSession);
}
}import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
public class MetricsHandlerDecorator extends WebSocketHandlerDecorator {
private final MetricsService metricsService;
public MetricsHandlerDecorator(WebSocketHandler delegate,
MetricsService metricsService) {
super(delegate);
this.metricsService = metricsService;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
metricsService.incrementConnections();
super.afterConnectionEstablished(session);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception {
metricsService.incrementMessages();
long start = System.currentTimeMillis();
try {
super.handleMessage(session, message);
} finally {
long duration = System.currentTimeMillis() - start;
metricsService.recordProcessingTime(duration);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
throws Exception {
metricsService.decrementConnections();
super.afterConnectionClosed(session, closeStatus);
}
}import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
import org.springframework.web.socket.handler.TextWebSocketHandler;
// Handler with per-connection state
public class StatefulHandler extends TextWebSocketHandler {
private int messageCount = 0; // Per-connection state
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
messageCount++;
session.sendMessage(new TextMessage(
"Message #" + messageCount + ": " + message.getPayload()));
}
}
// Configuration
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// Create new StatefulHandler instance per connection
registry.addHandler(
new PerConnectionWebSocketHandler(StatefulHandler.class),
"/stateful"
);
}
}import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
WebSocketHandler handler = new MyHandler();
// Chain decorators: exception handling → logging → rate limiting
handler = new ExceptionWebSocketHandlerDecorator(handler);
handler = new LoggingWebSocketHandlerDecorator(handler);
handler = new RateLimitingDecorator(handler);
registry.addHandler(handler, "/ws");
}
// Custom decorator
private static class RateLimitingDecorator extends WebSocketHandlerDecorator {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
public RateLimitingDecorator(WebSocketHandler delegate) {
super(delegate);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
throws Exception {
RateLimiter limiter = limiters.computeIfAbsent(
session.getId(),
id -> new RateLimiter(100) // 100 messages per minute
);
if (limiter.allowMessage()) {
super.handleMessage(session, message);
} else {
session.close(CloseStatus.POLICY_VIOLATION.withReason(
"Rate limit exceeded"));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
limiters.remove(session.getId());
super.afterConnectionClosed(session, closeStatus);
}
}
}