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
- Discard the invalid paging state and restart pagination from the first page (execute the query without setPagingState)
- Ensure driver protocol version matches the server's (v4/v5) and the driver is up to date; do not let the driver auto-downgrade
- Never modify, truncate, or base64-transcode the paging state bytes; store them opaquely and byte-exact
- 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
- Treat paging state as fully opaque bytes; never parse/transcode them
- Only reuse a cursor with the identical query that produced it
- Keep driver and protocol versions aligned with the server
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
- Invalid BATCH message type
- Invalid IP address ( . . . ) while deserializing inet…
- Invalid IP address while deserializing inet address
- Invalid query kind in BATCH messages. Must be 0 or 1 but…
- Provided frame does not appear to be Snappy compressed
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)