apache/cassandra · critical · IllegalStateException

Received a command with the thrift flag set. This means…

Error message

Received a command with the thrift flag set. This means thrift is in use in a mixed 3.0/3.X and 4.0+ cluster, which is unsupported. Make sure to stop using thrift before upgrading to 4.0

What it means

ReadCommand's serializer for messages from pre-4.0 (3.0/3.X) nodes throws IllegalStateException when the deserialized command has the thrift 'forThrift' flag set. Thrift was removed in 4.0; seeing this flag means an unsupported mixed-version cluster is still issuing Thrift-originated reads, and proceeding would silently do the wrong thing.

Solutions

  1. Stop all Thrift clients and migrate them to CQL (the 4.0+ native protocol) before continuing the rolling upgrade.
  2. Verify no nodes/clients use thrift: check start_rpc settings and application configs on 3.x nodes.
  3. Complete the cluster upgrade so all nodes are 4.0+ (which rejects thrift entirely), keeping versions homogeneous.
  4. If seen unexpectedly, check for stale 3.x nodes still joined to the cluster (nodetool status / describecluster).

Example fix

// before
// legacy app on 3.x node using thrift client against mixed cluster
cassandra.client.transport=THRIFT
// after
cqlSession = CqlSession.builder().addContactPoint(node).build(); // CQL 4 native protocol
Defensive patterns

Strategy: fallback

Validate before calling

// Before upgrading: audit all clients for thrift usage.
// On 3.x: grep start_rpc in cassandra.yaml; check netstat for port 9160 connections.

Try / catch

try { deserialize(cmd); } catch (IllegalStateException e) {
    if (e.getMessage().contains("thrift flag")) { alertOpsMixedVersionCluster(); haltUpgrade(); } else throw e;
}

Prevention

When it happens

Trigger: Rolling upgrade of a 3.0/3.X cluster to 4.0+ while some application or node still uses the Thrift protocol; an old node serializing a read command flagged for Thrift that a 4.0+ node tries to deserialize.

Common situations: Mixed 3.x/4.0 clusters during upgrade where legacy Thrift clients were never migrated to CQL; forgotten legacy applications using the old thrift API; upgrade runbooks that skipped the 'stop using thrift' prerequisite.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/db/ReadCommand.java:1465

            if (hasIndex)
            {
                IndexMetadata index = deserializeIndexMetadata(in, version, tableMetadata);
                Index.Group indexGroup =  Keyspace.openAndGetStore(tableMetadata).indexManager.getIndexGroup(index);
                if (indexGroup != null)
                    indexQueryPlan = indexGroup.queryPlanFor(rowFilter);
            }

            return deserializer.deserialize(in, version, schemaVersion, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, tableMetadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
        }

        public ReadCommand deserialize(DataInputPlus in, int version) throws IOException
        {
            Kind kind = Kind.fromOrdinal(in.readByte());
            int flags = in.readByte();
            // Shouldn't happen or it's a user error (see comment above) but
            // better complain loudly than doing the wrong thing.
            if (isForThrift(flags))
                throw new IllegalStateException("Received a command with the thrift flag set. "
                                                + "This means thrift is in use in a mixed 3.0/3.X and 4.0+ cluster, "
                                                + "which is unsupported. Make sure to stop using thrift before "
                                                + "upgrading to 4.0");

            int digestVersion = isDigest(flags) ? in.readUnsignedVInt32() : 0;
            TableId tableId = TableId.deserialize(in);

            Epoch schemaVersion = Epoch.EMPTY;
            if (version >= MessagingService.VERSION_60)
                schemaVersion = Epoch.serializer.deserialize(in);
            TableMetadata tableMetadata;
            try
            {
                tableMetadata = schema.getExistingTableMetadata(tableId);
            }
            catch (UnknownTableException e)
            {
                ClusterMetadata metadata = ClusterMetadata.current();

View on GitHub (pinned to 88fd0f6a0e)