jhy/jsoup · error · IOException
Resetting to invalid mark
Error message
Resetting to invalid mark
What it means
ControllableInputStream.reset() throws IOException('Resetting to invalid mark') when there is no valid mark (markPos < 0), i.e. reset() was called without a prior mark(), or the mark was invalidated. jsoup uses mark/reset internally for BOM/charset detection and lookahead; callers can also hit it via the stream's mark/reset contract.
Solutions
- Always call mark(readlimit) before reset(), with readlimit larger than bytes you'll read.
- Track whether mark is valid in your own wrapper; guard reset() accordingly.
- Don't reset after jsoup's parse/charset detection has consumed the stream; re-request or buffer instead.
- Wrap in BufferedInputStream with a generous buffer size if you need mark/reset yourself.
- For retryable reads, read fully into a byte[] and use ByteArrayInputStream instead of resetting.
Example fix
// before in.reset(); // no mark set // after in.mark(8192); // ... read header ... in.reset();
Defensive patterns
Strategy: validation
Validate before calling
boolean canReset = ((ControllableInputStream) in).markSupported() && markIsActive; // track your own mark state if (!canReset) skip reset;
Try / catch
try { in.reset(); } catch (IOException e) { if (e.getMessage().contains("invalid mark")) { /* reopen or re-buffer instead */ } } Prevention
- Always pair mark(readlimit) with reset(); size readlimit generously.
- Assume marks are invalidated after reading more than readlimit bytes.
- Don't reset streams after jsoup parse/charset detection consumed them.
- Prefer re-fetching or buffering bytes over stream rewinds.
When it happens
Trigger: Calling reset() on a ControllableInputStream (or wrapping stream) before any mark() call; calling reset() after reading beyond the readLimit given to mark(); mark cleared after jsoup finishes charset detection and the caller resets again.
Common situations: Wrapping jsoup's connection stream and hand-rolling mark/reset logic; reading more bytes than the mark limit during manual charset sniffing; reusing a stream after parse() consumed and invalidated marks.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of jhy/jsoup@9851ac5d9c (2026-09-08).
Data as JSON: /api/errors/0e8e1c03736fdf60.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/org/jsoup/internal/ControllableInputStream.java:190
newBuffer.put(outBuf);
outBuf = newBuffer;
}
outBuf.put(readBuf, 0, read);
if (capped) {
remaining -= read;
if (remaining <= 0) break;
}
}
outBuf.flip(); // Prepare the buffer for reading
return outBuf;
} finally {
SimpleBufferedInput.BufferPool.release(readBuf);
}
}
@SuppressWarnings("NonSynchronizedMethodOverridesSynchronizedMethod") // not synchronized in later JDKs
@Override public void reset() throws IOException {
if (markPos < 0) throw new IOException("Resetting to invalid mark");
buff.rewindToMark();
buff.clearMark();
truncated = false;
if (maxSize != 0) {
remaining = maxSize - markPos;
buff.capRemaining(remaining);
} else {
remaining = 0;
buff.uncap();
}
readPos = markPos; // readPos is used for progress emits
markPos = -1;
}
@SuppressWarnings("NonSynchronizedMethodOverridesSynchronizedMethod") // not synchronized in later JDKs
@Override public void mark(int readlimit) {
markPos = readPos;
buff.setMark();View on GitHub (pinned to 9851ac5d9c)