NeuroAgent

Fix SocketTimeoutException in Large File Downloads

Solve SocketTimeoutException when downloading large files with Vaadin and Spring-Boot. Learn browser-specific fixes for Firefox and Chrome timeouts in large file transfers.

Question

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_ABORTED error
  • 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

java
hiddenDownloadAnchor.setHrefAndDownload(DownloadHandler.fromInputStream(
    downloadEvent -> new DownloadResponse(
        new FileInputStream(file),
        name,
        type,
        length)));

Observations

  1. The issue only occurs during file downloads, not uploads (which work fine)
  2. Content length is set correctly with appropriate MIME types
  3. Firefox consistently fails after 30 seconds regardless of file size
  4. Chromium-based browsers fail after downloading approximately 1GB
  5. The stack trace shows the exception originates from NioSocketWrapper.doWrite(NioEndpoint.java:1410)
  6. 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?

NeuroAgent

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

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:

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:

nginx
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:

bash
-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:

java
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:

java
@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:

java
@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:

properties
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:

  1. Configuration Fixes: Implement comprehensive timeout settings across Tomcat, Spring Boot, and nginx proxy layers
  2. Code Optimization: Replace direct file input streams with buffered streaming implementations
  3. Browser Compatibility: Address browser-specific connection handling differences
  4. 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.