apache/cassandra · error

Invalid TTL: %s

Error message

Invalid TTL: %s

What it means

While deserializing a cell from the wire (or an SSTable via the same code path), Cell.Serializer reads the TTL field. A negative TTL is not representable in the object model and can only come from a corrupt or maliciously crafted payload, so deserialization fails immediately with an IOException, aborting the read of that message/partition.

Source

Thrown at src/java/org/apache/cassandra/db/rows/Cell.java:418

            V value = accessor.empty();
            if (hasValue)
            {
                if (helper.canSkipValue(column) || (path != null && helper.canSkipValue(path)))
                {
                    header.getType(column).skipValue(in);
                }
                else
                {
                    boolean isCounter = localDeletionTime == NO_DELETION_TIME && column.type.isCounter();

                    value = header.getType(column).read(accessor, in, DatabaseDescriptor.getMaxValueSize());
                    if (isCounter)
                        value = helper.maybeClearCounterValue(value, accessor);
                }
            }

            if (ttl < 0)
                throw new IOException("Invalid TTL: " + ttl);
            localDeletionTime = decodeLocalDeletionTime(localDeletionTime, ttl, helper);
            return accessor.factory().cell(column, timestamp, ttl, localDeletionTime, value, path);
        }

        public <T> long serializedSize(Cell<T> cell, ColumnMetadata column, LivenessInfo rowLiveness, SerializationHeader header)
        {
            long size = 1; // flags
            boolean hasValue = cell.valueSize() > 0;
            boolean isDeleted = cell.isTombstone();
            boolean isExpiring = cell.isExpiring();
            boolean useRowTimestamp = !rowLiveness.isEmpty() && cell.timestamp() == rowLiveness.timestamp();
            boolean useRowTTL = isExpiring && rowLiveness.isExpiring() && cell.ttl() == rowLiveness.ttl() && cell.localDeletionTime() == rowLiveness.localExpirationTime();

            if (!useRowTimestamp)
                size += header.timestampSerializedSize(cell.timestamp());

            if ((isDeleted || isExpiring) && !useRowTTL)
                size += header.localDeletionTimeSerializedSize(cell.localDeletionTime());

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Identify and drop/scrub the corrupt data source (`nodetool scrub` for SSTables, replay-truncate commitlog if needed).
  2. Verify internode network integrity (NIC/driver issues) if corruption appears in transit.
  3. Ensure node versions are compatible (same messaging version) to avoid misparsed fields.
  4. Restore affected data from backups.

Example fix

// before: writing TTL into a payload without bounds check
out.writeInt((int) ttlOrOverride);
// after
int ttl = (int) ttlOrOverride;
if (ttl < 0) throw new IllegalArgumentException("TTL must be >= 0");
out.writeInt(ttl);
Defensive patterns

Strategy: try-catch

Validate before calling

// validate before sending: reject negative TTLs at write time
if (ttl < 0) throw new IllegalArgumentException("Invalid TTL: " + ttl);

Try / catch

try { Cell.codec.deserialize(in, version, column, ...); } catch (IOException e) { markSourceCorrupt(e); }

Prevention

When it happens

Trigger: Receiving a mutated/corrupted internode message whose cell TTL field is negative; reading a corrupt SSTable region interpreted as TTL bytes; version-skew or deserialization of an attacker-crafted payload.

Common situations: Network corruption or buffer handling bugs; corrupted commitlog/SSTable files; fuzzing or crafted streaming payloads between nodes.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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