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.
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);
}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();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();// 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);// 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 usedimport 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 automatically selects the best transport based on browser capabilities and network conditions:
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);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 receivedHeartbeat Behavior:
h (heartbeat) frame periodicallyTime 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 gracefullyDisconnect Delay Behavior:
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 issuesBest Practice: Set disconnect delay to 2-3x heartbeat interval for optimal balance.
// 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// 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 deploymentsSession Cookie Behavior:
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);// 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 environmentsCache Behavior:
// 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 freshnessStreaming Behavior:
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// 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// 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');
});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();
}No Transport Available:
CORS Errors:
// Configure allowed origins
container.setAllowedOrigins("http://localhost:3000");
// Or suppress CORS for custom handling
SockJsServiceOptions options = new SockJsServiceOptions()
.setSuppressCors(true);Missing Heartbeats:
Heartbeat Configuration:
// Set appropriate heartbeat interval
// Too short: excessive overhead
// Too long: slow disconnect detection
SockJsServiceOptions options = new SockJsServiceOptions()
.setHeartbeatTime(25000); // 25 seconds (default)Best Practice: All SockJS components are thread-safe. Custom transport handlers must also be thread-safe.
Optimization:
Optimization:
WebSocket (Best):
XHR Streaming (Good):
Polling (Fallback):
Best Practice: Let SockJS automatically select transport. Only customize for specific requirements.
Symptoms: Client cannot establish SockJS connection.
Causes:
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);Symptoms: Sessions closing unexpectedly, heartbeat timeouts.
Causes:
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 framesSymptoms: Load balancer routing to wrong server, session state lost.
Causes:
Solutions:
// Enable session cookies
SockJsServiceOptions options = new SockJsServiceOptions()
.setSessionCookieNeeded(true);
// Configure load balancer for sticky sessions
// Verify cookie domain and path settingsSymptoms: Memory consumption increasing with session count.
Causes:
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// 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);
}// 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 { }// 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();
}