apache/iceberg · warning · IOException
No mark defined or has read past the previous mark limit
Error message
No mark defined or has read past the previous mark limit
What it means
MultiBufferInputStream supports mark/readmark only up to markLimit bytes read past the mark, because backward seeks beyond that would require buffers that were already discarded. reset() throws IOException('No mark defined or has read past the previous mark limit') when no mark is active or the read limit was exceeded. It is the standard InputStream mark/reset contract violation.
Solutions
- Call mark(readLimit) with a readLimit large enough to cover all lookahead before reading
- Ensure reset() is called at most once per mark(); re-mark after each reset
- Guard reset() with mark >= 0 checks or skip reset when no mark is defined
- If unlimited reset is needed, buffer the data yourself or re-open the stream and seek instead
- Catch IOException around reset() and fall back to re-seeking from a saved position
Example fix
// before stream.mark(16); // too small for actual lookahead ... read 100 bytes ... stream.reset(); // IOException: read past mark limit // after stream.mark(1024); // limit >= maximum expected lookahead ... read up to 1024 bytes ... stream.reset();
Defensive patterns
Strategy: try-catch
Validate before calling
// before reset, ensure a mark is active
if (stream.markLimit > 0 || stream.mark >= 0) { // track marks in wrapper if fields are private
stream.reset();
} else {
stream.seek(savedPosition); // fallback
} Try / catch
try {
stream.reset();
} catch (IOException e) {
stream.seek(savedPosition); // fallback to explicit seek
} Prevention
- Always pair mark(readLimit) with exactly one reset()
- Size readLimit to the maximum possible lookahead, plus margin
- Prefer explicit seek(bookmark) over mark/reset when positions are known
When it happens
Trigger: Calling reset() before any mark() call; calling reset() after reading more than the readLimit passed to mark(readLimit); calling reset() twice (the first reset discards the mark via discardMark()).
Common situations: Avro/Parquet container readers calling reset() after lookahead exceeded the limit; mark() called with a too-small readLimit; reusing a stream across records where a second reset() has no valid mark.
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
- Failed to get stream length
- Failed to get stream length: no open stream
- Failed to read header and fingerprint bytes
- Insufficient bytes in buffer
- Reached the end of stream with X bytes left to read
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/c58c5e84acaeac9a.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/io/MultiBufferInputStream.java:312
}
this.mark = position;
this.markLimit = mark + readlimit + 1;
if (current != null) {
markBuffers.add(current.duplicate());
}
}
@Override
public void reset() throws IOException {
if (mark >= 0 && position < markLimit) {
this.position = mark;
// replace the current iterator with one that adds back the buffers that
// have been used since mark was called.
this.iterator = Iterators.concat(markBuffers.iterator(), iterator);
discardMark();
nextBuffer(); // go back to the marked buffers
} else {
throw new IOException("No mark defined or has read past the previous mark limit");
}
}
private void discardMark() {
this.mark = -1;
this.markLimit = 0;
markBuffers = Lists.newArrayList();
}
@Override
public boolean markSupported() {
return true;
}
private boolean nextBuffer() {
if (!iterator.hasNext()) {
this.current = null;
return false;View on GitHub (pinned to 86d9c8fc54)