or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

adapter-support.mdclient-support.mdconfiguration.mdcore-websocket.mdhandlers.mdindex.mdserver-support.mdsockjs-client.mdsockjs-frames.mdsockjs-support.mdstomp-messaging.md
tile.json

handlers.mddocs/

Handler Support

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.

Capabilities

AbstractWebSocketHandler

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;
    }
}

TextWebSocketHandler

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
        }
    }
}

BinaryWebSocketHandler

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
        }
    }
}

WebSocketHandlerDecorator

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();
}

WebSocketHandlerDecoratorFactory

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);
}

ExceptionWebSocketHandlerDecorator

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);
}

LoggingWebSocketHandlerDecorator

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);
}

WebSocketSessionDecorator

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;
}

ConcurrentWebSocketSessionDecorator

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;
}

PerConnectionWebSocketHandler

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();
}

BeanCreatingHandlerProvider

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);
}

SessionLimitExceededException

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();
}

Usage Examples

Using TextWebSocketHandler

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));
    }
}

Using ConcurrentWebSocketSessionDecorator

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()));
    }
}

Using ConcurrentWebSocketSessionDecorator with DROP Strategy

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);
    }
}

Custom Handler Decorator

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);
    }
}

Using PerConnectionWebSocketHandler

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"
        );
    }
}

Chaining Multiple Decorators

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);
        }
    }
}