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
- 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
- Check that the buffer length passed to read() is > 0; a zero-length read array can legally return 0
- Wrap non-blocking sources (NIO channels) in a blocking adapter or load the data into a byte[] and use Jsoup.parse(String) instead
- 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
- Prefer loading bytes first (readAllBytes) and parsing from byte[]/String for non-blocking sources
- Never implement InputStream.read to return 0; return -1 at EOF
- Use blocking adapters for NIO channels
- Test custom streams with a reader loop that asserts read() != 0
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)