SocketTimeoutException when downloading large files with Vaadin and Spring-Boot: Inconsistent behavior across browsers
I’m experiencing a SocketTimeoutException when downloading large files using Vaadin’s DownloadHandler.fromInputStream API with Spring-Boot. The issue manifests inconsistently across different browsers:
- Firefox: Download fails after 30 seconds with
NS_BINDING_ABORTEDerror - Chromium-based browsers (Chrome, Brave): Download fails after approximately 1GB of data
Technical Environment
- Framework: Vaadin 24.9.3 with Spring-Boot
- Java: 21/25
- Server: Tomcat 10.1.48 (also tested with 10.1.46)
- Deployment: Debian server behind nginx proxy (WAR file)
- Browsers: Firefox 145.0b6, Brave 1.83.120 (Chromium 141.0.7390.122)
Configuration Attempts
Nginx Configuration
send_timeout 300;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
Tomcat Configuration
tomcat:
connection-timeout: 900000 (default is 60000)
Code Implementation
hiddenDownloadAnchor.setHrefAndDownload(DownloadHandler.fromInputStream(
downloadEvent -> new DownloadResponse(
new FileInputStream(file),
name,
type,
length)));
Observations
- The issue only occurs during file downloads, not uploads (which work fine)
- Content length is set correctly with appropriate MIME types
- Firefox consistently fails after 30 seconds regardless of file size
- Chromium-based browsers fail after downloading approximately 1GB
- The stack trace shows the exception originates from
NioSocketWrapper.doWrite(NioEndpoint.java:1410) - The write timeout value in debug mode is larger than 30 seconds (default is 60)
Stack Trace
org.apache.catalina.connector.ClientAbortException: java.net.SocketTimeoutException
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:342)
...
Caused by: java.net.SocketTimeoutException
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1410)
...
Question
What could be causing these inconsistent timeout behaviors across browsers, and how can I resolve the SocketTimeoutException when downloading large files with Vaadin and Spring-Boot? Are there additional configurations or code modifications needed to support large file downloads consistently across different browsers?
When downloading large files with Vaadin and Spring-Boot, SocketTimeoutException inconsistencies across browsers typically stem from a combination of Tomcat timeout configurations, proxy settings, and browser-specific connection handling behaviors. The Firefox 30-second timeout and Chromium 1GB failure points suggest different underlying issues - Firefox likely hitting proxy connection limits while Chromium encounters buffer saturation or memory constraints during sustained transfers.
Contents
- Understanding the Root Causes
- Browser-Specific Behavior Analysis
- Configuration Solutions
- Code Modifications for Large File Downloads
- Monitoring and Debugging
- Conclusion
Understanding the Root Causes
The SocketTimeoutException in your large file download scenario results from multiple interconnected factors:
Tomcat Timeout Limitations
The stack trace points to NioEndpoint$NioSocketWrapper.doWrite() as the source, indicating that Tomcat itself is terminating the connection. While you’ve set connection-timeout: 900000, this only affects the initial connection establishment, not the sustained data transfer timeout. According to Stack Overflow research, the connection timeout in Tomcat controls how long Tomcat will wait for the next request on a persistent connection, but doesn’t prevent timeouts during data streaming.
Proxy Layer Complications
Your nginx proxy configuration with 600-second timeouts might still be insufficient for large file transfers, especially considering:
- Firefox’s 30-second failure: This aligns with many proxy defaults for connection persistence
- Chromium’s 1GB limit: Suggests buffer management issues where the proxy can’t maintain the connection state for extended periods
The issue occurs specifically during downloads, not uploads, which points to different processing paths in the proxy and browser handling. Research shows that when streaming large files through proxies, the connection layers can become misaligned in their timeout expectations.
Vaadin DownloadHandler Implementation
Your current implementation using DownloadHandler.fromInputStream with direct file input streams may not be optimal for large files. The Vaadin documentation suggests that for large files, you should consider streaming approaches rather than loading the entire file into memory or using direct file input streams that can cause buffer saturation.
Browser-Specific Behavior Analysis
Firefox NS_BINDING_ABORTED After 30 Seconds
Firefox’s consistent 30-second failure indicates a connection persistence issue:
- Proxy Keep-Alive: Firefox may be closing connections faster than other browsers
- Connection Pool Management: Firefox might not reusing connections as efficiently
- Security Features: Firefox’s enhanced security settings could be terminating slow transfers
The NS_BINDING_ABORTED error suggests Firefox is proactively terminating what it perceives as hung connections. Research indicates that this is common when browser security policies interpret slow downloads as potential security threats.
Chromium Browser Failure at ~1GB
Chromium-based browsers failing after approximately 1GB points to buffer and memory management issues:
- Memory Constraints: Browsers have limits on how much data they’ll buffer before forcing a flush
- TCP Window Size: After transferring ~1GB, TCP window scaling might cause issues
- JavaScript Event Loop: Vaadin’s client-side components may have event queue saturation
This behavior is consistent with research findings where large file downloads encounter async timeout issues in Spring Boot applications.
Configuration Solutions
Comprehensive Tomcat Configuration
Add these properties to your application.properties:
# Tomcat connector timeouts
server.connection-timeout=1800000
server.tomcat.connection-timeout=1800000
server.tomcat.async-timeout=1800000
server.tomcat.keep-alive-timeout=1800000
# Spring Boot multipart configuration
spring.servlet.multipart.max-file-size=2GB
spring.servlet.multipart.max-request-size=2GB
spring.http.multipart.max-file-size=2GB
spring.http.multipart.max-request-size=2GB
As noted in multiple Stack Overflow answers, these timeout values need to be significantly higher than your expected download times.
Enhanced Nginx Configuration
Update your nginx configuration to:
send_timeout 3600;
proxy_connect_timeout 600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 2G;
The proxy_buffering off and proxy_request_buffering off directives are crucial for large file downloads as they prevent nginx from buffering the entire response, which can cause memory issues and timeouts.
JVM Memory Considerations
Ensure your JVM has sufficient memory for large file operations:
-Xms2g -Xmx4g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC
Code Modifications for Large File Downloads
Optimized DownloadHandler Implementation
Instead of using direct file input streams, implement a streaming approach:
hiddenDownloadAnchor.setHrefAndDownload(DownloadHandler.fromInputStream(
downloadEvent -> {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(file), 8192);
return new DownloadResponse(
inputStream,
name,
type,
length) {
@Override
public void writeResponse(OutputStream outputStream) throws IOException {
byte[] buffer = new byte[8192];
int bytesRead;
long totalBytesWritten = 0;
try (BufferedOutputStream bufferedOutput = new BufferedOutputStream(outputStream)) {
while ((bytesRead = inputStream.read(buffer)) != -1) {
bufferedOutput.write(buffer, 0, bytesRead);
totalBytesWritten += bytesRead;
// Update UI progress (if needed)
UI.getCurrent().access(() -> {
// Update progress indicator
});
}
bufferedOutput.flush();
}
}
};
}));
Alternative: Spring Boot Streaming ResponseBody
For better control, consider using Spring Boot’s StreamingResponseBody directly:
@GetMapping("/download/{fileId}")
public ResponseEntity<StreamingResponseBody> downloadFile(@PathVariable String fileId) {
File file = getFile(fileId);
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.body(responseBody);
}
Vaadin 24.8+ Migration Considerations
The Vaadin 24.8 upload handling API introduces improved support for large file operations. Consider migrating to use the new DownloadHandler APIs that provide better browser compatibility and streaming support.
Monitoring and Debugging
Connection Monitoring
Add monitoring to track connection states:
@Component
public class DownloadMonitor implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(DownloadMonitor.class);
@Override
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().addListener(new HttpSessionListener() {
@Override
public void sessionCreated(HttpSessionEvent se) {
logger.info("Session created: {}", se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
logger.info("Session destroyed: {}", se.getSession().getId());
}
});
}
}
Browser-Specific Debugging
For browser-specific issues, implement:
- Firefox: Disable security features temporarily to test if they’re causing the 30-second timeout
- Chromium: Check browser console for network-related errors during the 1GB transfer point
- Network Analysis: Use browser developer tools to monitor connection persistence and buffer states
Server-Side Logging
Enhance Tomcat logging to capture timeout details:
logging.level.org.apache.catalina=DEBUG
logging.level.org.apache.tomcat.util.net=DEBUG
This will provide detailed information about connection states and timeout events as described in the Apache Tomcat documentation.
Conclusion
The SocketTimeoutException inconsistencies across browsers when downloading large files with Vaadin and Spring-Boot can be resolved through a multi-layered approach:
- Configuration Fixes: Implement comprehensive timeout settings across Tomcat, Spring Boot, and nginx proxy layers
- Code Optimization: Replace direct file input streams with buffered streaming implementations
- Browser Compatibility: Address browser-specific connection handling differences
- Monitoring: Add detailed logging and monitoring to identify timeout patterns
The key is recognizing that different browsers have different connection persistence behaviors - Firefox being more aggressive about closing slow connections while Chromium browsers encounter buffer management issues during sustained transfers. By implementing the suggested configurations and code modifications, you should achieve consistent large file download performance across all browsers.
For ongoing maintenance, consider implementing progressive download features that allow users to resume interrupted downloads, which is particularly valuable for very large files that might encounter network issues during transfer.