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

  1. Ensure sections are requested in strictly increasing offset order
  2. Reset/recreate the CompressedInputStream (open a new stream session) if backward seeks are required
  3. Check for retry logic that replays already-read ranges and skip already-consumed sections instead
  4. 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

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


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/1cd5caaaf718ee8c. Report an issue: GitHub.