apache/cassandra · error · IllegalStateException
stream can only move forward
Error message
stream can only move forward
What it means
CompressedInputStream.position moves the decompressed stream to a given uncompressed offset during compressed SSTable streaming. Because the stream reads whole compression chunks sequentially, it can only seek forward; attempting to rewind past already-consumed uncompressed data throws IllegalStateException. This is an internal ordering invariant for CassandraCompressedStreamReader.
Source
Thrown at src/java/org/apache/cassandra/db/streaming/CompressedInputStream.java:98
this.input = input;
this.checksumType = checksumType;
this.validateChecksumChance = validateChecksumChance;
compressionParams = compressionInfo.parameters();
compressedChunks = Iterators.forArray(compressionInfo.chunks());
compressedChunk = ByteBuffer.allocateDirect(compressionParams.chunkLength());
}
/**
* Invoked when crossing into the next {@link SSTableReader.PartitionPositionBounds} section
* in {@link CassandraCompressedStreamReader#read(DataInputPlus)}.
* Will skip 1..n compressed chunks of the original sstable.
*/
public void position(long position) throws IOException
{
if (position < uncompressedChunkPosition + buffer.position())
throw new IllegalStateException("stream can only move forward");
if (position >= uncompressedChunkPosition + buffer.limit())
{
loadNextChunk();
// uncompressedChunkPosition = position - (position % compressionParams.chunkLength())
uncompressedChunkPosition = position & -compressionParams.chunkLength();
}
buffer.position(Ints.checkedCast(position - uncompressedChunkPosition));
}
@Override
protected void reBuffer() throws IOException
{
if (uncompressedChunkPosition < 0)
throw new IllegalStateException("position(long position) wasn't called first");
/*View on GitHub (pinned to 88fd0f6a0e)
Solutions
- Ensure sections are requested in strictly increasing offset order
- Reset/recreate the CompressedInputStream (open a new stream session) if backward seeks are required
- Check for retry logic that replays already-read ranges and skip already-consumed sections instead
- Verify compression chunk sizes match between sender and receiver
Example fix
// before
stream.position(earlierOffset); // throws
// after
if (earlierOffset > currentUncompressedPosition()) {
stream.position(earlierOffset);
} else {
// reopen stream or skip handling
} Defensive patterns
Strategy: validation
Validate before calling
if (targetPosition < lastPosition) throw new IllegalArgumentException("backward seek not allowed"); Type guard
boolean canSeek = pos -> pos >= stream.currentUncompressedPosition();
Try / catch
try { stream.position(pos); } catch (IllegalStateException e) { reopenStream(pos); } Prevention
- Request stream sections in ascending offset order
- Never replay ranges in retry logic; skip already-read data
- Keep one stream instance per session; recreate on rewind
When it happens
Trigger: Calling position(long) with an offset smaller than uncompressedChunkPosition + buffer.position(), i.e. trying to seek backwards within or before the currently buffered chunk.
Common situations: Retransmission/retry logic replaying a range that was already read; stream session rollback after a failure; tests simulating random-access reads; custom StreamSession sector handling that requests out-of-order sections.
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
- Table's compressor can not accept any dictionary:
- Node should be either source or destination in the movement
- Can't join the ring because bootstrap hasn't completed.
- %s function resource has no function name
- Root-level resource can't have a parent
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/1cd5caaaf718ee8c.
Report an issue: GitHub.