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

  1. Always call mark(readlimit) before reset(), with readlimit larger than bytes you'll read.
  2. Track whether mark is valid in your own wrapper; guard reset() accordingly.
  3. Don't reset after jsoup's parse/charset detection has consumed the stream; re-request or buffer instead.
  4. Wrap in BufferedInputStream with a generous buffer size if you need mark/reset yourself.
  5. 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

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)