{"record":{"id":"0a55618e400c6bbb","repo":"jhy/jsoup","slug":"resetting-to-invalid-mark-0a5561","errorCode":null,"errorMessage":"Resetting to invalid mark","messagePattern":"Resetting to invalid mark","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/jsoup/internal/SimpleBufferedInput.java","lineNumber":173,"sourceCode":"        if (bufPos < bufLength || lookahead >= 0) return true;\n        if (inReadFully) return false;\n\n        int next = in.read();\n        if (next == -1) {\n            inReadFully = true;\n            return false;\n        }\n        lookahead = next;\n        return true;\n    }\n\n    void setMark() {\n        bufMark = bufPos;\n    }\n\n    void rewindToMark() throws IOException {\n        if (bufMark < 0)\n            throw new IOException(\"Resetting to invalid mark\");\n        bufPos = bufMark;\n    }\n\n    void clearMark() {\n        bufMark = -1;\n    }\n\n    private void compact() {\n        if (byteBuf == null || bufPos == 0) return;\n        int keepFrom = bufMark >= 0 ? bufMark : bufPos;\n        if (keepFrom <= 0) return;\n\n        int remaining = bufLength - keepFrom;\n        if (remaining > 0) {\n            System.arraycopy(byteBuf, keepFrom, byteBuf, 0, remaining);\n        }\n        bufLength = remaining;\n        bufPos -= keepFrom;","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/jhy/jsoup/blob/9851ac5d9c576c6888910b5a51a2362bbc978959/src/main/java/org/jsoup/internal/SimpleBufferedInput.java#L155-L191","documentation":"SimpleBufferedInput.rewindToMark is the internal buffer primitive backing mark/reset. It throws IOException('Resetting to invalid mark') when bufMark < 0, meaning setMark() was never called or clearMark() already ran. It enforces the standard Java InputStream mark/reset contract at the buffer level.","triggerScenarios":"ControllableInputStream.reset() delegating to a buffer whose mark was cleared (e.g. after prior reset or after jsoup's charset detection completes); calling reset() with no matching outstanding mark(); double-reset on the same mark.","commonSituations":"Custom stream wrappers around jsoup internals; code paths that call reset() twice; reading after jsoup's detection code cleared the mark, then attempting a rewind.","solutions":["Ensure each reset() has a preceding setMark()/mark() that hasn't been cleared or consumed.","Guard: only rewind when a mark is active (track it in your wrapper).","Treat mark as single-use: clear your 'marked' flag after reset.","If you need repeated rewinds, buffer the region in a byte[] yourself.","Upgrade jsoup if the invalid mark arises from internal detection flows (fixed in later versions)."],"exampleFix":"// before\nbuf.setMark();\nbuf.clearMark();\nbuf.rewindToMark(); // throws\n// after\nbuf.setMark();\nbuf.rewindToMark();\nbuf.clearMark();","handlingStrategy":"validation","validationCode":"if (!markActive) throw new IllegalStateException(\"no active mark; cannot rewind\");","typeGuard":null,"tryCatchPattern":"try { rewind(); } catch (IOException e) { if (e.getMessage().contains(\"invalid mark\")) { /* re-buffer region and retry */ } }","preventionTips":["Track mark lifecycle explicitly: set -> use -> clear, never rewind after clear.","Treat marks as single-shot, not reusable.","Buffer bytes you need to re-read instead of relying on mark/reset.","Prefer BufferedInputStream with a known buffer size for your own mark/reset needs."],"tags":["io","stream","mark-reset","buffer"],"backgroundTag":"invalid-state-transition","analyzedSha":"9851ac5d9c576c6888910b5a51a2362bbc978959","analyzedAt":"2026-09-08T15:22:04.931Z","contentChangedAt":"2026-09-08T15:22:04.931Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}