or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

containers.mdinbound.mdindex.mdoutbound.mdprotocols.mdsockjs.md
tile.json

sockjs.mddocs/

SockJS Fallback Support

SockJS provides WebSocket emulation fallback for browsers that don't support native WebSocket. It offers multiple transport mechanisms with automatic fallback, heartbeat management, and session cookie handling. This ensures reliable real-time communication across all browsers and network environments.

Capabilities

SockJS Configuration

Enable and configure SockJS fallback support on server-side WebSocket containers.

/**
 * SockJS configuration builder for ServerWebSocketContainer.
 * Provides fluent API for configuring SockJS service options.
 * All methods return this instance for method chaining.
 *
 * @since 4.1
 */
public static class SockJsServiceOptions {

    /**
     * Set the TaskScheduler for SockJS heartbeat and timeouts.
     * If not set, a default ThreadPoolTaskScheduler is created.
     * Must be initialized before use.
     *
     * @param taskScheduler the TaskScheduler instance (must not be null)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if taskScheduler is null
     */
    public SockJsServiceOptions setTaskScheduler(TaskScheduler taskScheduler);

    /**
     * Set the URL for the SockJS client library.
     * Clients can load SockJS from this URL.
     *
     * @param clientLibraryUrl the client library URL (may be null)
     * @return this options instance for fluent chaining
     */
    public SockJsServiceOptions setClientLibraryUrl(String clientLibraryUrl);

    /**
     * Set the streaming transport bytes limit.
     * Minimum bytes that must be sent before closing streaming request.
     * Default: 128KB
     * Higher values reduce reconnection overhead but consume more resources.
     *
     * @param streamBytesLimit bytes limit for streaming transports (must be > 0)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if streamBytesLimit <= 0
     */
    public SockJsServiceOptions setStreamBytesLimit(int streamBytesLimit);

    /**
     * Configure whether session cookie (JSESSIONID) is needed.
     * Useful for load balancing and sticky sessions.
     * Default: false
     *
     * @param sessionCookieNeeded true if session cookie required
     * @return this options instance for fluent chaining
     */
    public SockJsServiceOptions setSessionCookieNeeded(boolean sessionCookieNeeded);

    /**
     * Set the heartbeat interval in milliseconds.
     * Server sends heartbeat frames to keep connection alive.
     * Default: 25 seconds
     * Lower values detect disconnects faster but increase overhead.
     *
     * @param heartbeatTime heartbeat interval in milliseconds (must be > 0)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if heartbeatTime <= 0
     */
    public SockJsServiceOptions setHeartbeatTime(long heartbeatTime);

    /**
     * Set the disconnect delay in milliseconds.
     * Time to wait before closing session after connection loss.
     * Default: 5 seconds
     * Higher values handle network interruptions better but delay cleanup.
     *
     * @param disconnectDelay disconnect delay in milliseconds (must be >= 0)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if disconnectDelay < 0
     */
    public SockJsServiceOptions setDisconnectDelay(long disconnectDelay);

    /**
     * Set the HTTP message cache size.
     * Number of messages to cache per session.
     * Default: 100
     * Higher values help with burst traffic but use more memory.
     *
     * @param httpMessageCacheSize cache size for HTTP transports (must be > 0)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if httpMessageCacheSize <= 0
     */
    public SockJsServiceOptions setHttpMessageCacheSize(int httpMessageCacheSize);

    /**
     * Enable or disable native WebSocket transport.
     * When disabled, only HTTP fallback transports are used.
     * Default: true
     *
     * @param webSocketEnabled true to enable WebSocket transport
     * @return this options instance for fluent chaining
     */
    public SockJsServiceOptions setWebSocketEnabled(boolean webSocketEnabled);

    /**
     * Set custom transport handlers.
     * Overrides default SockJS transport handlers.
     *
     * @param transportHandlers array of TransportHandler instances (may be empty)
     * @return this options instance for fluent chaining
     */
    public SockJsServiceOptions setTransportHandlers(TransportHandler... transportHandlers);

    /**
     * Set custom message codec for SockJS frames.
     * Controls message encoding/decoding format.
     *
     * @param messageCodec the SockJsMessageCodec instance (must not be null)
     * @return this options instance for fluent chaining
     * @throws IllegalArgumentException if messageCodec is null
     */
    public SockJsServiceOptions setMessageCodec(SockJsMessageCodec messageCodec);

    /**
     * Configure whether to suppress CORS headers.
     * When true, SockJS won't add CORS headers (custom CORS config used).
     * Default: false
     *
     * @param suppressCors true to suppress CORS headers
     * @return this options instance for fluent chaining
     */
    public SockJsServiceOptions setSuppressCors(boolean suppressCors);
}

Basic Usage

Enable SockJS with Defaults

import org.springframework.integration.websocket.ServerWebSocketContainer;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;

// Create server container
ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");

// Set handshake handler (required)
container.setHandshakeHandler(new DefaultHandshakeHandler());

// Enable SockJS with default settings
container.withSockJs();

// Container now supports both WebSocket and SockJS fallback transports
container.start();

Custom SockJS Configuration

import org.springframework.integration.websocket.ServerWebSocketContainer.SockJsServiceOptions;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

// Create custom SockJS configuration
SockJsServiceOptions sockJsOptions = new SockJsServiceOptions()
    .setHeartbeatTime(30000)         // 30 seconds heartbeat
    .setDisconnectDelay(10000)       // 10 seconds disconnect delay
    .setSessionCookieNeeded(true)    // Enable session cookies for sticky sessions
    .setStreamBytesLimit(256 * 1024) // 256 KB streaming limit
    .setHttpMessageCacheSize(200);   // Cache 200 messages per session

// Create and configure TaskScheduler
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("sockjs-");
scheduler.initialize();
sockJsOptions.setTaskScheduler(scheduler);

// Create container with SockJS
ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(sockJsOptions);

container.start();

SockJS Client Library URL

// Configure custom SockJS client library location
SockJsServiceOptions options = new SockJsServiceOptions()
    .setClientLibraryUrl("https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js");

ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(options);

Disable WebSocket Transport

// Force use of HTTP fallback transports only (for testing or special requirements)
SockJsServiceOptions options = new SockJsServiceOptions()
    .setWebSocketEnabled(false);

ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(options);

// Now only HTTP-based transports (XHR streaming, polling, etc.) are used

Runtime Registration with Custom Scheduler

import org.springframework.scheduling.TaskScheduler;

// For containers registered at runtime, provide explicit scheduler
ServerWebSocketContainer container = new ServerWebSocketContainer("/dynamic-sockjs");
container.setHandshakeHandler(handshakeHandler);

// Set scheduler before enabling SockJS
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
container.setSockJsTaskScheduler(scheduler);

// Enable SockJS
container.withSockJs();

// Register at runtime
container.start();

SockJS Transports

SockJS automatically selects the best transport based on browser capabilities and network conditions:

Transport Priority (Default)

  1. WebSocket - Native WebSocket (if enabled and supported)
  2. XHR Streaming - HTTP streaming using XMLHttpRequest
  3. XDR Streaming - HTTP streaming using XDomainRequest (IE 8-9)
  4. IFrame EventSource - Server-Sent Events via iframe
  5. IFrame HTML File - Forever iframe technique
  6. XHR Polling - Long polling using XMLHttpRequest
  7. XDR Polling - Long polling using XDomainRequest (IE 8-9)
  8. IFrame XHR Polling - Long polling via iframe
  9. JSON-P Polling - JSONP-based long polling

Custom Transport Handlers

import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.*;

// Create custom transport handler list
TransportHandler[] customTransports = new TransportHandler[] {
    new WebSocketTransportHandler(),
    new XhrStreamingTransportHandler(),
    new XhrPollingTransportHandler(),
    new JsonpPollingTransportHandler()
};

// Configure with custom transports only
SockJsServiceOptions options = new SockJsServiceOptions()
    .setTransportHandlers(customTransports);

ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(options);

Heartbeat and Timeout Configuration

Heartbeat Mechanism

SockJS sends periodic heartbeat frames to keep connections alive and detect disconnects:

// Configure heartbeat (default: 25 seconds)
SockJsServiceOptions options = new SockJsServiceOptions()
    .setHeartbeatTime(20000); // 20 seconds

// Server sends heartbeat frame every 20 seconds
// Client expects heartbeat; timeout if not received

Heartbeat Behavior:

  • Server sends h (heartbeat) frame periodically
  • Client must receive heartbeat within timeout period
  • Missing heartbeats indicate connection loss
  • Heartbeat overhead: minimal (~10 bytes per heartbeat)

Disconnect Delay

Time to wait before considering a session disconnected:

// Configure disconnect delay (default: 5 seconds)
SockJsServiceOptions options = new SockJsServiceOptions()
    .setDisconnectDelay(15000); // 15 seconds

// If no communication for 15 seconds, session is closed
// Useful for handling network interruptions gracefully

Disconnect Delay Behavior:

  • Waits for reconnection attempts
  • Allows temporary network issues to resolve
  • Higher values: better resilience, delayed cleanup
  • Lower values: faster cleanup, less resilience

Combined Timing Configuration

SockJsServiceOptions options = new SockJsServiceOptions()
    .setHeartbeatTime(25000)      // Heartbeat every 25 seconds
    .setDisconnectDelay(10000);   // Close after 10 seconds of silence

// Heartbeat keeps long-lived connections alive
// Disconnect delay handles temporary network issues

Best Practice: Set disconnect delay to 2-3x heartbeat interval for optimal balance.

CORS and Session Cookies

CORS Configuration

// Use default CORS handling
ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setAllowedOrigins("http://localhost:3000", "https://app.example.com");
container.withSockJs();

// Or suppress SockJS CORS for custom configuration
SockJsServiceOptions options = new SockJsServiceOptions()
    .setSuppressCors(true);

container.withSockJs(options);
// Implement custom CORS via Spring Security or filters

Session Cookie Support

// Enable session cookies for sticky session load balancing
SockJsServiceOptions options = new SockJsServiceOptions()
    .setSessionCookieNeeded(true);

ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(options);

// JSESSIONID cookie ensures requests route to same server
// Critical for stateful session management in clustered deployments

Session Cookie Behavior:

  • JSESSIONID cookie set on first request
  • Subsequent requests include cookie
  • Enables sticky session routing in load balancers
  • Required for session state in clustered environments

Advanced Configuration

Custom Message Codec

import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec;

// Create custom codec (e.g., for specialized message format)
SockJsMessageCodec codec = new Jackson2SockJsMessageCodec();

SockJsServiceOptions options = new SockJsServiceOptions()
    .setMessageCodec(codec);

ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs");
container.setHandshakeHandler(handshakeHandler);
container.withSockJs(options);

HTTP Message Caching

// Configure message cache for HTTP transports
// Larger cache helps with burst traffic and slow clients
SockJsServiceOptions options = new SockJsServiceOptions()
    .setHttpMessageCacheSize(500); // Cache 500 messages per session

// Default is 100 messages
// Increase for high-throughput applications
// Decrease for memory-constrained environments

Cache Behavior:

  • Messages cached when client not ready
  • Cached messages sent when client reconnects
  • Cache cleared after successful delivery
  • Memory usage: ~100 bytes per cached message

Streaming Byte Limit

// Configure streaming transport behavior
SockJsServiceOptions options = new SockJsServiceOptions()
    .setStreamBytesLimit(512 * 1024); // 512 KB

// Streaming transports close and reconnect after this limit
// Prevents infinite streams from consuming resources
// Higher values reduce reconnection overhead
// Lower values improve connection freshness

Streaming Behavior:

  • Streaming transports send data continuously
  • After limit reached, connection closes and reopens
  • Prevents resource exhaustion
  • Higher values: fewer reconnections, more resource usage

Complete Example

import org.springframework.integration.websocket.ServerWebSocketContainer;
import org.springframework.integration.websocket.ServerWebSocketContainer.SockJsServiceOptions;
import org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter;
import org.springframework.integration.websocket.outbound.WebSocketOutboundMessageHandler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;

// Configure TaskScheduler
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("sockjs-");
scheduler.initialize();

// Configure SockJS with all options
SockJsServiceOptions sockJsOptions = new SockJsServiceOptions()
    .setTaskScheduler(scheduler)
    .setClientLibraryUrl("https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js")
    .setHeartbeatTime(30000)              // 30 second heartbeat
    .setDisconnectDelay(10000)            // 10 second disconnect delay
    .setSessionCookieNeeded(true)         // Enable sticky sessions
    .setStreamBytesLimit(256 * 1024)      // 256 KB streaming limit
    .setHttpMessageCacheSize(200)         // Cache 200 messages
    .setWebSocketEnabled(true)            // Allow WebSocket upgrade
    .setSuppressCors(false);               // Use SockJS CORS

// Create server container with SockJS
ServerWebSocketContainer container = new ServerWebSocketContainer("/sockjs-endpoint");
container.setHandshakeHandler(new DefaultHandshakeHandler());
container.setAllowedOrigins("http://localhost:3000");
container.withSockJs(sockJsOptions);

// Configure inbound and outbound adapters
WebSocketInboundChannelAdapter inbound =
    new WebSocketInboundChannelAdapter(container);
inbound.setOutputChannel(inputChannel);

WebSocketOutboundMessageHandler outbound =
    new WebSocketOutboundMessageHandler(container);

// Start container
container.start();

// Client connects to: http://localhost:8080/sockjs-endpoint
// SockJS client automatically selects best transport

Client-Side Usage

JavaScript SockJS Client

// Load SockJS client library
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>

// Connect to SockJS endpoint
var socket = new SockJS('http://localhost:8080/sockjs-endpoint');

socket.onopen = function() {
    console.log('Connected via', socket.protocol);
    socket.send('Hello from client!');
};

socket.onmessage = function(e) {
    console.log('Received:', e.data);
};

socket.onclose = function() {
    console.log('Connection closed');
};

// SockJS automatically:
// - Tries WebSocket first
// - Falls back to HTTP transports if needed
// - Handles reconnection
// - Processes heartbeats

SockJS with STOMP

// Using STOMP over SockJS
var socket = new SockJS('http://localhost:8080/sockjs-stomp');
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
    console.log('Connected: ' + frame);

    // Subscribe to topic
    stompClient.subscribe('/topic/messages', function(message) {
        console.log('Message:', message.body);
    });

    // Send message
    stompClient.send('/app/send', {}, 'Message content');
});

Error Handling

Scheduler Initialization Errors

TaskScheduler Not Initialized:

ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// Must initialize before use
scheduler.initialize();
sockJsOptions.setTaskScheduler(scheduler);

Scheduler Already Shutdown:

// Check scheduler state
if (scheduler.isShutdown()) {
    // Create new scheduler
    scheduler = new ThreadPoolTaskScheduler();
    scheduler.initialize();
}

Transport Selection Errors

No Transport Available:

  • SockJS client automatically tries all transports
  • If all fail, connection cannot be established
  • Server logs transport failures
  • Client should implement retry logic

CORS Errors:

// Configure allowed origins
container.setAllowedOrigins("http://localhost:3000");

// Or suppress CORS for custom handling
SockJsServiceOptions options = new SockJsServiceOptions()
    .setSuppressCors(true);

Heartbeat Timeout Errors

Missing Heartbeats:

  • Client doesn't receive heartbeat within timeout
  • Session is closed after disconnect delay
  • Logged at WARN level
  • Normal behavior for network issues

Heartbeat Configuration:

// Set appropriate heartbeat interval
// Too short: excessive overhead
// Too long: slow disconnect detection
SockJsServiceOptions options = new SockJsServiceOptions()
    .setHeartbeatTime(25000); // 25 seconds (default)

Thread Safety

SockJS Service Thread Safety

  • SockJsService: Thread-safe for concurrent client connections
  • Transport Handlers: Thread-safe (one per transport type)
  • Message Codec: Thread-safe (stateless or thread-local state)
  • Task Scheduler: Thread-safe for concurrent task scheduling

Session Management Thread Safety

  • Session Storage: Uses thread-safe data structures
  • Heartbeat Scheduling: Thread-safe task scheduling
  • Message Caching: Thread-safe cache operations

Best Practice: All SockJS components are thread-safe. Custom transport handlers must also be thread-safe.

Performance Considerations

Heartbeat Overhead

  • Frequency: One heartbeat per interval per session
  • Size: ~10 bytes per heartbeat
  • Network: Minimal bandwidth usage
  • CPU: Minimal processing overhead

Optimization:

  • Use default 25-second interval for most cases
  • Increase interval for high-session-count scenarios
  • Decrease interval for faster disconnect detection

Message Cache Performance

  • Memory: ~100 bytes per cached message
  • Lookup: O(1) via HashMap
  • Cleanup: Automatic after delivery

Optimization:

  • Set cache size based on expected burst traffic
  • Monitor cache hit rate
  • Adjust based on client connection patterns

Transport Performance

WebSocket (Best):

  • Lowest latency
  • Lowest overhead
  • Full-duplex communication

XHR Streaming (Good):

  • Low latency
  • One-way streaming
  • Higher overhead than WebSocket

Polling (Fallback):

  • Higher latency
  • Higher overhead
  • Works in all environments

Best Practice: Let SockJS automatically select transport. Only customize for specific requirements.

Troubleshooting

Issue: SockJS connection fails

Symptoms: Client cannot establish SockJS connection.

Causes:

  1. Handshake handler not configured
  2. CORS restrictions
  3. Scheduler not initialized
  4. Transport handlers not available

Solutions:

// Ensure handshake handler is set (required)
container.setHandshakeHandler(new DefaultHandshakeHandler());

// Configure CORS
container.setAllowedOrigins("http://localhost:3000");

// Initialize scheduler
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
sockJsOptions.setTaskScheduler(scheduler);

// Verify SockJS is enabled
container.withSockJs(sockJsOptions);

Issue: Heartbeats not working

Symptoms: Sessions closing unexpectedly, heartbeat timeouts.

Causes:

  1. Scheduler not configured
  2. Heartbeat interval too short
  3. Network issues preventing heartbeat delivery

Solutions:

// Configure scheduler
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
sockJsOptions.setTaskScheduler(scheduler);

// Adjust heartbeat interval
sockJsOptions.setHeartbeatTime(30000); // 30 seconds

// Check network connectivity
// Verify firewall rules allow heartbeat frames

Issue: Session cookies not working

Symptoms: Load balancer routing to wrong server, session state lost.

Causes:

  1. Session cookies not enabled
  2. Load balancer not configured for sticky sessions
  3. Cookie domain/path misconfiguration

Solutions:

// Enable session cookies
SockJsServiceOptions options = new SockJsServiceOptions()
    .setSessionCookieNeeded(true);

// Configure load balancer for sticky sessions
// Verify cookie domain and path settings

Issue: High memory usage

Symptoms: Memory consumption increasing with session count.

Causes:

  1. Message cache too large
  2. Too many cached messages per session
  3. Sessions not being cleaned up

Solutions:

// Reduce message cache size
sockJsOptions.setHttpMessageCacheSize(50); // Default: 100

// Monitor session count
Map<String, WebSocketSession> sessions = container.getSessions();
logger.info("Active sessions: {}", sessions.size());

// Verify session cleanup
// Check disconnect delay configuration

Types

Transport Handler

// From Spring Framework
interface TransportHandler {
    /**
     * Get the transport type.
     *
     * @return the transport type (never null)
     */
    TransportType getTransportType();

    /**
     * Handle transport request.
     * Thread-safe: can be called concurrently.
     *
     * @param request the HTTP request (must not be null)
     * @param response the HTTP response (must not be null)
     * @param handler the WebSocket handler (must not be null)
     * @param session the SockJS session (must not be null)
     */
    void handleRequest(
        ServerHttpRequest request,
        ServerHttpResponse response,
        WebSocketHandler handler,
        SockJsSession session);
}

SockJS Message Codec

// From Spring Framework
interface SockJsMessageCodec {
    /**
     * Encode messages into SockJS frame format.
     *
     * @param messages messages to encode (must not be null)
     * @return encoded frame string (never null)
     * @throws IllegalArgumentException if messages is null
     */
    String encode(String... messages);

    /**
     * Decode SockJS frame content.
     *
     * @param content frame content to decode (must not be null)
     * @return decoded messages array (never null)
     * @throws IllegalArgumentException if content is null
     */
    String[] decode(String content);

    /**
     * Decode SockJS frame from input stream.
     *
     * @param content input stream to decode (must not be null)
     * @return decoded messages array (never null)
     * @throws IOException if reading fails
     * @throws IllegalArgumentException if content is null
     */
    String[] decodeInputStream(InputStream content) throws IOException;
}

// Default implementation
class Jackson2SockJsMessageCodec implements SockJsMessageCodec { }

Task Scheduler

// From Spring Framework
interface TaskScheduler {
    /**
     * Schedule task at fixed rate.
     *
     * @param task the task to schedule (must not be null)
     * @param period the period between executions (must be > 0)
     * @return scheduled future (never null)
     * @throws IllegalArgumentException if task is null or period <= 0
     */
    ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period);

    /**
     * Schedule task with fixed delay.
     *
     * @param task the task to schedule (must not be null)
     * @param delay the delay between executions (must be > 0)
     * @return scheduled future (never null)
     * @throws IllegalArgumentException if task is null or delay <= 0
     */
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay);
}

// Common implementation
class ThreadPoolTaskScheduler implements TaskScheduler {
    public void setPoolSize(int poolSize);
    public void setThreadNamePrefix(String threadNamePrefix);
    public void initialize();
    public boolean isShutdown();
}