jhy/jsoup · error · SocketTimeoutException

Read timeout

Error message

Read timeout

What it means

ControllableInputStream.read wraps the underlying stream with an overall deadline and optional byte cap. Before each underlying read it calls expired(); if the total elapsed time exceeds the configured timeout it throws SocketTimeoutException('Read timeout'). This means the connection produced no usable data within the overall time budget, even if individual reads were trickling in.

Solutions

  1. Raise the timeout: Jsoup.connect(url).timeout(30000) or higher for slow hosts.
  2. Verify the URL/host responds (curl --max-time) to distinguish slowness from outage.
  3. Retry with backoff for transient slowness.
  4. Check proxy/VPN and network path health if all targets time out.
  5. Use Response with maxBytes only when needed — large caps on slow links need proportionally larger timeouts.

Example fix

// before
Document doc = Jsoup.connect(url).timeout(1000).get();
// after
Document doc = Jsoup.connect(url)
    .timeout(30000)
    .get();
Defensive patterns

Strategy: retry

Validate before calling

long timeoutMs = connection.request().timeout();
if (timeoutMs < 5000 && isSlowHost(url)) raise timeout before connecting;

Try / catch

try { doc = Jsoup.connect(url).timeout(30000).get(); } catch (SocketTimeoutException e) { /* retry with backoff or fail gracefully */ }

Prevention

When it happens

Trigger: Jsoup.connect(url).timeout(ms).execute()/parse() where the server is slow or stalls; reads keep returning small chunks so total elapsed time exceeds the timeout; .maxBytes caps combined with slow networks; a dead proxy accepting connections but never sending data.

Common situations: Fetching slow or overloaded sites; scraping through a degraded proxy or VPN; long-tail responses on mobile networks; timeout set too low for large downloads on slow links.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of jhy/jsoup@9851ac5d9c (2026-09-08). Data as JSON: /api/errors/cb321a9c94e3176b. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/org/jsoup/internal/ControllableInputStream.java:106

            // interrupted latches, because parse() may call twice
            interrupted = true;
            return -1;
        }
        if (capped && remaining <= 0) {
            if (checkTruncated()) return -1;
            contentLength = readPos;
            emitProgress();
            return -1;
        }

        if (capped && len > remaining)
            len = remaining; // don't read more than desired, even if available
        if (capped) buff.capRemaining(remaining);
        else buff.uncap();

        while (true) { // loop trying to read until we get some data or hit the overall timeout, if we have one
            if (expired())
                throw new SocketTimeoutException("Read timeout");

            try {
                final int read = super.read(b, off, len);
                if (read == -1) { // completed
                    contentLength = readPos;
                } else {
                    if (capped && read > 0) {
                        remaining -= read; // track bytes returned to the caller
                    }
                    // todo: use long progress values in the public API; saturate until that is available
                    readPos = read > Integer.MAX_VALUE - readPos ? Integer.MAX_VALUE : readPos + read;
                }
                emitProgress();
                return read;
            } catch (SocketTimeoutException e) {
                if (expired() || timeout == 0)
                    throw e;
            }

View on GitHub (pinned to 9851ac5d9c)