apache/cassandra · critical · java.io.IOException

Corrupt flags value for clustering prefix (isStatic flag…

Error message

Corrupt flags value for clustering prefix (isStatic flag set): <flags>

What it means

When deserializing an unfiltered (row/static-row) from an sstable or commit log, the extended flags indicate whether the next unfiltered is a static row or a regular row with a clustering prefix. If the isStatic bit is set in extendedFlags but the code is reading a clustering prefix, the on-disk data is structurally corrupt. This IOException guards the deserializer against misinterpreting the byte stream.

Solutions

  1. Run nodetool scrub on the affected table to repair or drop corrupt sstables
  2. Restore the affected sstables/commit logs from a known-good backup
  3. Check disk health (SMART, filesystem check) and replace failing hardware
  4. File an issue with the exact sstable/version if corruption appears reproducible on clean data

Example fix

// before
// node fails reading corrupt sstable during compaction/read
// after
nodetool scrub --skip-corrupted <keyspace> <table>  // then repair re-replicates from other replicas
Defensive patterns

Strategy: retry

Try / catch

try { iterateSstables(...); } catch (IOException e) { if (e.getMessage().contains("Corrupt flags value")) { scheduleScrub(keyspace, table); alertCorruption(e); } else throw e; }

Prevention

When it happens

Trigger: UnfilteredSerializer deserialization path calling Deserializer.prepare(flags, extendedFlags) with UnfilteredSerializer.isStatic(extendedFlags) == true while reading what should be a clustering prefix.

Common situations: Bitrot/corruption on disk, truncated or hand-edited sstable/commit-log files, bugs in other writers, or restoring files from mismatched backups.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/db/ClusteringPrefix.java:664

        private long nextHeader;

        private int nextSize;
        private ClusteringPrefix.Kind nextKind;
        private int deserializedSize;
        private byte[][] nextValues;
        private final ValueAccessor<byte[]> accessor = ByteArrayAccessor.instance;

        public Deserializer(ClusteringComparator comparator, DataInputPlus in, SerializationHeader header)
        {
            this.comparator = comparator;
            this.in = in;
            this.serializationHeader = header;
        }

        public void prepare(int flags, int extendedFlags) throws IOException
        {
            if (UnfilteredSerializer.isStatic(extendedFlags))
                throw new IOException("Corrupt flags value for clustering prefix (isStatic flag set): " + flags);

            this.nextIsRow = UnfilteredSerializer.kind(flags) == Unfiltered.Kind.ROW;
            this.nextKind = nextIsRow ? Kind.CLUSTERING : Kind.fromOrdinal(in.readByte());
            this.nextSize = nextIsRow ? comparator.size() : in.readUnsignedShort();
            this.deserializedSize = 0;

            // The point of the deserializer is that some of the clustering prefix won't actually be used (because they are not
            // within the bounds of the query), and we want to reduce allocation for them. So we only reuse the values array
            // between elements if 1) we haven't returned the previous element (if we have, nextValues will be null) and 2)
            // nextValues is of the proper size. Note that the 2nd condition may not hold for range tombstone bounds, but all
            // rows have a fixed size clustering, so we'll still save in the common case.
            if (nextValues == null || nextValues.length != nextSize)
                this.nextValues = new byte[nextSize][];
        }

        public <T> int compareNextTo(ClusteringBoundOrBoundary<T> bound) throws IOException
        {
            if (bound.isTop())

View on GitHub (pinned to 88fd0f6a0e)