1
0
Files
2026-09-04 00:52:18 +05:30

5.6 KiB

5. The limits: origin, size, and which one actually fires

Previous: 4. STOMP · Next: 6. Choosing


Origin: checked by default, and the check is real

The WebSocket handshake is an ordinary HTTP GET, and the browser's same-origin policy does not apply to it. Without a server-side check, a page on any site could open a WebSocket to your server with the user's cookies attached. Spring checks Origin by default. WebSocketHandshakeOriginTest varies only that header (handshake-origin.txt):

no Origin header          -> HTTP/1.1 101
Origin: http://localhost  -> HTTP/1.1 101
Origin: https://evil...   -> HTTP/1.1 403

The middle row is the policy working. The first row is the one to understand: a client that sends no Origin at all is allowed through, which is why every Java test in this module connects without ceremony — and why Origin is a browser control, not an authentication mechanism. For a real cross-origin front end use setAllowedOriginPatterns("https://app.example.com"); setAllowedOrigins("*") is rejected outright when credentials are allowed.

Size: two limits, and the documented one is not the one that fires

WebSocketTransportRegistration.setMessageSizeLimit defaults to 64 KB and is what every guide tells you to raise. Underneath it sits the servlet container's own WebSocket buffer, which /diag reports as:

"ws.container.defaultMaxTextMessageBufferSize":8192

Spring's SubProtocolWebSocketHandler does not support partial messages, so the container has to assemble each text message whole before Spring sees it. Cross the container's ceiling and the connection is closed — WebSocket status 1009, The decoded text message was too big for the output buffer and the endpoint does not support partial messages — before Spring's limit is ever consulted.

StompSizeLimitDefaultBufferTest binary-searches for the exact ceiling (websocket-size-limits.txt):

container buffer largest body delivered smallest rejected
8 192 (Tomcat default) 16 459 16 522
16 384 16 446 16 496
32 768 32 615 32 665
65 536 65 350 65 400

Body length is the JSON string field; the STOMP frame adds about 90 bytes of headers on top, so each row is a frame just under 16 KB, 16 KB, 32 KB and 64 KB respectively.

Read that table honestly: the ceiling behaves like max(16 KB, buffer). Setting the buffer to 16 KB changes nothing from the default; above 16 KB the ceiling follows it. I have not reduced the 16 KB floor to a line in Tomcat's source and will not claim a mechanism I have not read. What is certain, and is the point:

Spring's 64 KB messageSizeLimit is not reached on a stock Tomcat. The real ceiling is about 16 KB, it is enforced by the container, and raising Spring's limit alone does nothing whatsoever.

The fix is a ServletServerContainerFactoryBean (WebSocketBufferConfig, active under the bigframes profile):

@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
    ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
    container.setMaxTextMessageBufferSize(256 * 1024);
    container.setMaxBinaryMessageBufferSize(256 * 1024);
    return container;
}

The two failures look different, and only one is diagnosable

With the buffer at 256 KB, Spring's limit finally decides — and it fails better:

 60 KB body -> delivered, body length 61440
 80 KB body -> NOT delivered, connection dropped
org.springframework.messaging.simp.stomp.StompConversionException: The configured STOMP buffer size limit of 65536 bytes has been exceeded

Spring logs the exception, names the limit, and sends a STOMP ERROR frame the client can see. The container's refusal does none of that. So the practical advice is not just "raise the buffer" — it is raise the container buffer above Spring's limit deliberately, so that the limit that fires is the one that tells you it fired.

The client has the same limit

A WebSocketStompClient built on StandardWebSocketClient inherits the same 8 KB default, and hits it on its own session first. That produced a false positive while writing these tests: a message that looked rejected by the server was actually refused by the client. SizeProbe raises the client side to 1 MB before measuring anything.

Send buffer and send time

Two limits that bite in production rather than in tests, both set in WebSocketConfig:

  • setSendBufferSizeLimit (512 KB here) — how much undelivered output Spring will hold for one slow client before closing its session. Without it, one client on hotel wifi can grow the heap until the process dies.
  • setSendTimeLimit (20 s here) — how long a single send may take before the session is closed.

Both exist because a broadcast is only as fast as its slowest subscriber, and a WebSocket session has no equivalent of TCP's back-pressure reaching your application code. This is precisely the gap RSocket closes, and the reason the next article exists.


Previous: 4. STOMP · Next: 6. Choosing