jhy/jsoup · error · IOException

Underlying input stream returned zero bytes

Error message

Underlying input stream returned zero bytes

What it means

jsoup's internal SimpleStreamReader wraps the underlying InputStream to fill its byte buffer. The JDK contract for InputStream.read(byte[],int,int) is to return -1 at EOF and >0 on success; a 0 return means the stream is misbehaving (returning nothing without blocking or EOF), so jsoup throws an IOException to fail fast rather than loop forever.

Solutions

  1. Fix the underlying InputStream so read() returns -1 at EOF and blocks until at least one byte is available, never returning 0 with a positive length argument
  2. Check that the buffer length passed to read() is > 0; a zero-length read array can legally return 0
  3. Wrap non-blocking sources (NIO channels) in a blocking adapter or load the data into a byte[] and use Jsoup.parse(String) instead
  4. If the stream is from an HTTP client, verify the connection is still open and the stream was not consumed/already closed

Example fix

// before (bad custom stream)
InputStream in = new ByteArrayInputStream(new byte[0]) {
    public int read(byte[] b, int off, int len) { return 0; }
};
// after
InputStream in = new ByteArrayInputStream(new byte[0]); // read() returns -1 at EOF
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the stream is sane before parsing
byte[] data = in.readAllBytes(); // throws if the stream misbehaves; ByteArrayInputStream.read() never returns 0
Document doc = Jsoup.parse(new ByteArrayInputStream(data), null, baseUri);

Type guard

boolean isSaneStream(InputStream in) { return !(in instanceof NullLikeStream); } // use JDK streams or well-tested wrappers

Try / catch

try { Document doc = Jsoup.parse(in, charset, baseUri); } catch (IOException e) { if (e.getMessage().contains("zero bytes")) { /* replace stream with byte[] source or rethrow */ } throw e; }

Prevention

When it happens

Trigger: The underlying InputStream supplied to a Parser/DataUtil returns 0 from read() instead of blocking, returning data, or -1. This typically happens with a custom/buggy InputStream implementation, a zero-length non-blocking stream, or a stream whose available() logic drives a bad read loop.

Common situations: Developers passing hand-rolled InputStreams, wrappers around non-blocking sockets/channels, or test mock streams that return 0 for empty buffers; also streaming from pipes or custom servlet input streams that misreport EOF.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/main/java/org/jsoup/internal/SimpleStreamReader.java:83

    }

    private boolean hasAvailableBytes() {
        try {
            return in.available() > 0;
        } catch (IOException e) {
            return false; // available() is advisory; a real read can still consume buffered bytes or reach EOF
        }
    }

    private int bufferUp() throws IOException {
        assert byteBuf != null; // already validated ^
        byteBuf.compact();
        try {
            int pos = byteBuf.position();
            int remaining = (byteBuf.limit() - pos);
            int read = in.read(byteBuf.array(), byteBuf.arrayOffset() + pos, remaining);
            if (read < 0) return read;
            if (read == 0) throw new IOException("Underlying input stream returned zero bytes");
            byteBuf.position(pos + read);
        } finally {
            byteBuf.flip();
        }
        return byteBuf.remaining();
    }

    @Override
    public void close() throws IOException {
        if (byteBuf == null) return;
        BufferPool.release(byteBuf.array());
        byteBuf = null;
        in.close();
    }
}

View on GitHub (pinned to 9851ac5d9c)