What does calling withSockJS() on a STOMP endpoint registration do, and why would you use it?
answer
- addEndpoint("/ws").withSockJS()
- native WS → streaming → long-polling
- proxies/firewalls block Upgrade
- extra HTTP endpoints + /ws/info + iframe
- needs sticky sessions behind LB
basics
~20 swithSockJS() enables SockJS fallback on a STOMP endpoint. If the browser or a proxy can't use a raw WebSocket, SockJS emulates one over HTTP transports (XHR streaming, XHR polling), so the app still works everywhere.
solid answer
~40 sIn your WebSocketMessageBrokerConfigurer you register a STOMP endpoint with registry.addEndpoint("/ws").withSockJS(). SockJS is a client+server protocol that gives you a WebSocket-like API but degrades gracefully: it first tries a native WebSocket, and if that fails (old browser, restrictive proxy, corporate firewall that blocks the Upgrade handshake) it falls back to HTTP-based transports such as XHR/SSE streaming and XHR long-polling. Enabling it makes the server expose extra HTTP endpoints under the path (e.g. /ws/info, /ws/{server}/{session}/xhr) that the SockJS client uses. Because some of those transports are plain HTTP and use an iframe, SockJS has origin/CSRF implications you must account for. You'd use it whenever you can't guarantee end-to-end native WebSocket support; if you fully control the environment, raw WebSocket is lighter.
code
java · 20 lines@Configuration
@EnableWebSocketMessageBroker
public class WsConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("https://app.example.com")
.withSockJS() // enable fallback transports
.setHeartbeatTime(25_000)
.setStreamBytesLimit(512 * 1024); // bytes before recycling a streaming request
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}
// JS client: const sock = new SockJS('/ws'); const stomp = Stomp.over(sock);go deeper
Know that withSockJS() adds a fallback so clients without native WebSocket still connect.
Explain the transport ladder and that it adds HTTP endpoints under the path.
Discuss CSRF/iframe/CDN implications and sticky-session needs behind a load balancer.
Weigh SockJS overhead vs raw WebSocket per deployment topology and configure transports/CSP deliberately.
**STOMP over WebSocket in Spring:** Spring's messaging stack lets a browser open a WebSocket and speak STOMP (Simple Text Oriented Messaging Protocol) — a frame-based pub/sub protocol (CONNECT, SUBSCRIBE, SEND, MESSAGE frames). You configure it by implementing `WebSocketMessageBrokerConfigurer` and overriding `registerStompEndpoints(StompEndpointRegistry registry)`. **What withSockJS() is:** `registry.addEndpoint("/ws").withSockJS()` turns on **SockJS emulation** for that endpoint. SockJS is an independent library (server side ships with `spring-websocket`, client side is `sockjs-client` in JS) whose whole job is: *give application code a WebSocket-like object, but transparently fall back to other transports when a real WebSocket is unavailable.* **Why a real WebSocket sometimes fails:** A WebSocket needs an HTTP `Upgrade` handshake. Some old browsers don't support it; more commonly, **restrictive corporate proxies, load balancers, or firewalls** strip or block the Upgrade header, so the connection silently dies. SockJS detects this and switches transports. **Transport ladder (best to worst):** 1. **WebSocket** — native, full duplex. 2. **HTTP streaming** — XHR streaming, Server-Sent Events (EventSource), or an htmlfile/iframe technique; server holds the response open and pushes chunks. 3. **HTTP long-polling** — XHR polling; client repeatedly requests, server holds until a message or timeout. **Server-side effect:** enabling SockJS makes Spring register additional **plain-HTTP endpoints** beneath the path: `GET /ws/info` (the client probes this first for server capabilities and to check same-origin/cookies), and per-session URLs like `/ws/{server-id}/{session-id}/xhr`, `/xhr_streaming`, `/eventsource`, `/htmlfile`, `/websocket`. So `/ws` is no longer a single WebSocket URL — it's a small family of URLs. **Configuration knobs on SockJsServiceRegistration** (returned by `withSockJS()`): `setSessionCookieNeeded(...)`, `setClientLibraryUrl(...)` (the iframe HTML loads sockjs-client from a CDN by default — override for CSP/offline), `setHeartbeatTime(...)`, `setStreamBytesLimit(...)`, `setDisconnectDelay(...)`, `setSuppressCors(...)`, `setTransportHandlers(...)` (e.g. to disable a transport). **Security implications (the gotcha):** Because fallback transports are ordinary HTTP requests — and one technique uses a hidden **iframe** — SockJS has cross-origin exposure a raw WebSocket doesn't. Two things follow: (1) Spring Security requires that STOMP CONNECT messages carry a **CSRF token** by default (it enforces same-origin for the WebSocket handshake; SockJS' HTTP transports mean you can't rely on the browser's WebSocket same-origin rules alone). (2) The iframe transport needs a Content-Security-Policy that permits it, and by default it fetches the SockJS client script from a CDN URL, which you often must override. **When to use it:** Use SockJS when clients live in uncontrolled environments (public internet, corporate networks, older browsers) and you need robust connectivity. Skip it (use a bare WebSocket) when you control the whole path (internal tooling, modern clients, a proxy you know passes Upgrade) — you save the extra endpoints, iframe, and polling overhead. Note the JS client must match: use `new SockJS('/ws')` (not `new WebSocket(...)`) when the server has SockJS enabled. **Edge cases:** SockJS session URLs are sticky — behind a load balancer you need **sticky sessions** (session affinity) because the HTTP transport requests for one logical session must reach the same server. Heartbeats keep long-polling/streaming connections and proxies alive.
- Why do SockJS deployments usually require sticky sessions behind a load balancer?HTTP fallback transports (XHR streaming/polling) issue multiple separate HTTP requests for one logical SockJS session, and the server holds per-session state in memory. Those requests must all land on the same instance, so you need session affinity.
- How does the JS client differ when SockJS is enabled versus a raw WebSocket?With SockJS you connect via new SockJS('/ws') (an http(s):// URL) instead of new WebSocket('ws://.../ws'). The SockJS client picks the transport; if you point a raw WebSocket at a SockJS endpoint it won't speak the SockJS protocol and fails.
saying these in an interview costs you the question
- Thinking SockJS replaces STOMP (it's a transport layer; STOMP still runs on top)
- Claiming SockJS uses a WebSocket URL (ws://) — it uses http(s):// and probes /ws/info first
- Believing fallback transports are full-duplex like WebSocket (they emulate it over half-duplex HTTP)