apache/cassandra · error · ProtocolException

Invalid value for the paging state

Error message

Invalid value for the paging state

What it means

PagingState is the opaque cursor a client sends back to continue a paged query. deserialize() parses it for the requested protocol version; if the bytes cannot be decoded (malformed, truncated, wrong content) an IOException is caught and rethrown as this ProtocolException. The client sent a paging state the server cannot interpret.

Solutions

  1. Discard the invalid paging state and restart pagination from the first page (execute the query without setPagingState)
  2. Ensure driver protocol version matches the server's (v4/v5) and the driver is up to date; do not let the driver auto-downgrade
  3. Never modify, truncate, or base64-transcode the paging state bytes; store them opaquely and byte-exact
  4. Only reuse a paging state with the exact same query (same table, bound values, ordering) that produced it

Example fix

// before: reusing an old/corrupt cursor blindly
statement.setPagingState(storedBytes);
// after: fall back to first page on ProtocolException
try {
    statement.setPagingState(storedBytes);
} catch (ProtocolException e) {
    statement = statement.unsetPagingState(); // restart pagination
}
Defensive patterns

Strategy: fallback

Validate before calling

boolean validPagingState(byte[] ps) { return ps != null && ps.length > 0; }
// only attach if produced by this exact query's previous page

Type guard

boolean isOpaqueCursor(Object o) { return o instanceof byte[] && ((byte[]) o).length > 0; }

Try / catch

try { stmt.setPagingState(cursor); return session.execute(stmt); }
catch (ProtocolException e) { return session.execute(stmt.unsetPagingState()); // restart from page 1 }

Prevention

When it happens

Trigger: Sending a paging state byte string that fails both legacy and modern format detection/parsing for the given protocol version — corrupted cursor, bytes from a different cluster/table, manually crafted paging state, or paging state fetched with a protocol version the driver downgrades/mangles.

Common situations: Driver bug or downgraded protocol version serializing v5+ paging state incorrectly; copying paging state between different queries/tables; storing and re-sending a truncated cursor (e.g. in a URL or cache that altered bytes); mixing driver/server versions during upgrades.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/service/pager/PagingState.java:127

             * lengths and allocate huge byte arrays for readWithVIntLength() or,
             * to a lesser extent, readWithShortLength()
             */

            if (protocolVersion.isGreaterThan(ProtocolVersion.V3))
            {
                if (isModernSerialized(bytes)) return modernDeserialize(bytes, protocolVersion);
                if (isLegacySerialized(bytes)) return legacyDeserialize(bytes, ProtocolVersion.V3);
            }

            if (protocolVersion.isSmallerThan(ProtocolVersion.V4))
            {
                if (isLegacySerialized(bytes)) return legacyDeserialize(bytes, protocolVersion);
                if (isModernSerialized(bytes)) return modernDeserialize(bytes, ProtocolVersion.V4);
            }
        }
        catch (IOException e)
        {
            throw new ProtocolException("Invalid value for the paging state");
        }

        throw new ProtocolException("Invalid value for the paging state");
    }

    /*
     * Modern serde (> VERSION_3)
     */

    private ByteBuffer modernSerialize() throws IOException
    {
        DataOutputBuffer out = new DataOutputBufferFixed(modernSerializedSize());
        writeWithVIntLength(null == partitionKey ? EMPTY_BYTE_BUFFER : partitionKey, out);
        writeWithVIntLength(null == rowMark ? EMPTY_BYTE_BUFFER : rowMark.mark, out);
        out.writeUnsignedVInt32(remaining);
        out.writeUnsignedVInt32(remainingInPartition);
        return out.buffer(false);
    }

View on GitHub (pinned to 88fd0f6a0e)