Debugging WebSocket Disconnect in Spring Boot
How a hidden Tomcat buffer limit caused silent WebSocket disconnects — and how to fix it for large STOMP payloads.
You’ve built a real-time app with Spring Boot and WebSockets. Everything works — chat messages
fly
back and forth, updates appear instantly, and all feels right with the world.
Then, you send your first
real
payload — a Base64-encoded audio clip, maybe 70 KB. Suddenly… silence. No error in the browser
console except a polite reconnect. No ERROR in your Spring Boot logs.
The client
(browser) thinks
the server (Spring Boot) disappeared, while the server has no idea anything happened.
Welcome to the WebSocket black hole — a place where perfectly valid connections go to die quietly. This is the story of a multi-day debugging journey and the single line of log output that finally cracked the mystery.
1. Problem Statement & Environment
The objective was to implement a feature recording audio in the browser via `MediaRecorder`, encoding it as a Base64 string, and transmitting it to a Spring Boot backend using a STOMP WebSocket connection.
Client-side code snippet:
// app.js
stompClient.publish({
destination: "/app/audio",
body: JSON.stringify(payload)
});
Server-side handler:
@MessageMapping("/audio")
public void handleAudioMessage(AudioMessage audioMessage) {
logger.info("Received audio message!");
}
2. Investigation & Elimination
Checkpoint 1: Application-Level Message Size Limits
Initial hypothesis pointed to Spring's WebSocket message size limits.
# This only configures the STOMP message size, not the underlying transport buffer
spring.websocket.messaging.stomp.message-size-limit=512000
However, this property alone is insufficient because it does not affect the underlying Tomcat transport layer's
buffer size. This represents a Leaky Abstraction: developers expect Spring properties to manage the
entire stack, but the underlying container's constraints leak through.
Checkpoint 2: Security Configuration
Secondary hypothesis involved Spring Security blocking the request. Disabling CSRF and CORS provided no
resolution.
3. Root Cause Analysis
When the size of the message exceeds the buffer size, the connection is closed with a status code of 1009.
o.s.w.s.h.LoggingWebSocketHandlerDecorator : StandardWebSocketSession[...] closed with CloseStatus[code=1009, reason=The decoded text message was too big for the output buffer and the endpoint does not support partial messages]
logging.level.org.springframework.web.socket=DEBUG
logging.level.org.springframework.messaging.simp=DEBUG
CloseStatus[code=1009, reason=The decoded text message was too big for the output buffer...]
This default is defined in Tomcat's internal Constants.java and, crucially, there are no standard Spring Boot properties to override it.
4. Solution Implementation
The effective resolution required bypassing high-level Spring configuration and directly customizing the embedded Tomcat container. Specifically, the `WsServerContainer` needed explicit configuration to increase `setMaxTextMessageBufferSize` and `setMaxBinaryMessageBufferSize` beyond the default 8KB.
@Bean
public WebServerFactoryCustomizer tomcatCustomizer() {
return factory -> factory.addContextCustomizers(context ->
context.addServletContainerInitializer((c, ctx) -> {
ctx.setAttribute(WsServerContainer.class.getName(), new WsServerContainer(ctx) {
{
setDefaultMaxTextMessageBufferSize(512 * 1024); // 512KB
setDefaultMaxBinaryMessageBufferSize(512 * 1024);
}
});
}, null));
}
5. Key Takeaways
Modern frameworks like Spring Boot provide excellent abstractions, but they sit atop deep technology stacks (like Tomcat). When high-level configurations strictly fail to resolve resource limit issues, it is often necessary to inspect and configure the underlying container's defaults.
6. Community Impact & Contributions
Following this investigation, I contributed back to the community by reporting the findings to the Spring Boot team.
- GitHub Issue: Spring Boot Issue #47944
- Outcome: The issue was reviewed and accepted. The documentation has been updated to clarify these buffer limits.
- Future Roadmap: A design decision is pending to potentially add a dedicated configuration property for this, making the explicit bean definition unnecessary in future versions.