apache/cassandra · error · MarshalException
A local expiration time should not be negative
Error message
A local expiration time should not be negative
What it means
Same validator as the TTL check: ExpiringLivenessInfo.validate() rejects a negative localExpirationTime, the wall-clock expiration timestamp computed as now + TTL. A negative value is impossible for valid expiration data, so MarshalException is thrown to keep corrupt values out of the storage engine.
Source
Thrown at src/java/org/apache/cassandra/db/LivenessInfo.java:336
@Override
public void digest(Digest digest)
{
super.digest(digest);
// As of 5.0, local expiration times are encoded as unsigned integers on disk, so we can do the
// same thing here to populate the digest. This supports extended TTLs, but also maintains digest
// compatibility with previous versions, avoiding false digest mismatches during upgrades.
digest.updateWithInt(Cell.deletionTimeLongToUnsignedInteger(localExpirationTime));
digest.updateWithInt(ttl);
}
@Override
public void validate()
{
if (ttl < 0)
throw new MarshalException("A TTL should not be negative");
if (localExpirationTime < 0)
throw new MarshalException("A local expiration time should not be negative");
}
@Override
public int dataSize()
{
return super.dataSize()
+ TypeSizes.sizeof(ttl)
+ TypeSizes.sizeof(localExpirationTime);
}
@Override
public LivenessInfo withUpdatedTimestamp(long newTimestamp)
{
return new ExpiringLivenessInfo(newTimestamp, ttl, localExpirationTime);
}
@OverrideView on GitHub (pinned to 88fd0f6a0e)
Solutions
- Run `nodetool scrub` on affected tables if negative values come from stored data.
- Fix any code that constructs expiration times to compute now + ttl with non-negative inputs.
- Verify system clocks (NTP) on nodes writing expiration data.
Example fix
// before
ExpiringLivenessInfo exp = new ExpiringLivenessInfo(ttl, localExpirationTime, nowInSeconds); // localExpirationTime < 0 possible
// after
if (localExpirationTime < 0) throw new IllegalArgumentException("invalid expiration");
ExpiringLivenessInfo exp = new ExpiringLivenessInfo(ttl, localExpirationTime, nowInSeconds); Defensive patterns
Strategy: validation
Validate before calling
if (localExpirationTime < 0) throw new IllegalArgumentException("localExpirationTime must be >= 0"); Type guard
long safeExpiration(long ttl, long now) { return Math.max(0, now + Math.max(0, ttl)); } Try / catch
try { readOrWrite(); } catch (MarshalException e) { if (e.getMessage().contains("expiration")) { triggerScrubAndAlert(); } else throw e; } Prevention
- Keep node clocks NTP-synchronized.
- Never hand-serialize expiration data; use the storage-engine APIs.
- Scrub SSTables after suspected corruption before replaying data.
When it happens
Trigger: Deserializing a cell whose localExpirationTime field is negative (corrupt SSTable, bad internode message); constructing ExpiringLivenessInfo directly with a negative expiration; extreme clock manipulation producing negative epoch values.
Common situations: Disk corruption or failed compaction leftovers; hand-rolled serialization in tools/tests; system clock set before epoch in exotic environments.
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
- A TTL should not be negative
- %s must be positive value <= %dB, but was %dB
- Too many bytes for comparator
- Invalid Timestamp:
- A TTL should not be negative
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/fa7155f5f78bc262.
Report an issue: GitHub.