apache/cassandra · error · InvalidRequestException

A TTL must be greater or equal to 0, but was <ttl>

Error message

A TTL must be greater or equal to 0, but was <ttl>

What it means

A bound TTL that decodes to a negative int is rejected: TTL semantics require 0 (no expiry) or a positive number of seconds. Attributes.getTimeToLive throws InvalidRequestException 'A TTL must be greater or equal to 0, but was <ttl>'.

Source

Thrown at src/java/org/apache/cassandra/cql3/Attributes.java:140

            return metadata.params.defaultTimeToLive;

        // byte[0] and null are the same for Int32Type.  UNSET_BYTE_BUFFER is also byte[0] but we rely on pointer
        // identity, so need to check this after checking that
        if (ByteBufferUtil.EMPTY_BYTE_BUFFER.equals(tval))
            return 0;

        try
        {
            Int32Type.instance.validate(tval);
        }
        catch (MarshalException e)
        {
            throw new InvalidRequestException("Invalid TTL value: " + tval);
        }

        int ttl = Int32Type.instance.compose(tval);
        if (ttl < 0)
            throw new InvalidRequestException("A TTL must be greater or equal to 0, but was " + ttl);

        if (ttl > MAX_TTL)
            throw new InvalidRequestException(String.format("ttl is too large. requested (%d) maximum (%d)", ttl, MAX_TTL));

        if (metadata.params.defaultTimeToLive != LivenessInfo.NO_TTL && ttl == LivenessInfo.NO_TTL)
            return LivenessInfo.NO_TTL;

        ExpirationDateOverflowHandling.maybeApplyExpirationDateOverflowPolicy(metadata, ttl, false);

        return ttl;
    }

    public void collectMarkerSpecification(VariableSpecifications boundNames)
    {
        if (timestamp != null)
            timestamp.collectMarkerSpecification(boundNames, this);
        if (timeToLive != null)
            timeToLive.collectMarkerSpecification(boundNames, this);

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Clamp or validate the TTL client-side: if (ttl < 0) ttl = 0; or reject before executing
  2. Use 0 to mean 'no TTL' instead of negative sentinels
  3. Fix the calculation producing the negative duration (e.g. expiry - now when expiry already passed)
  4. Re-run the statement with a non-negative TTL

Example fix

// before
int ttl = (int) Duration.between(now, expiry).getSeconds(); // may be negative
// after
int ttl = Math.max(0, (int) Duration.between(now, expiry).getSeconds());
Defensive patterns

Strategy: validation

Validate before calling

int safeTtl(int requested) {
    if (requested < 0)
        throw new IllegalArgumentException("TTL must be >= 0; use 0 for no expiry");
    return requested;
}

Type guard

boolean isValidTtl(Integer ttl) { return ttl != null && ttl >= 0; }

Try / catch

try {
    session.execute(bs);
} catch (InvalidQueryException e) {
    if (e.getMessage().startsWith("A TTL must be greater or equal to 0"))
        log.error("Negative TTL bound; use 0 for no-expiry, not negative sentinels");
    throw e;
}

Prevention

When it happens

Trigger: 'USING TTL ?' bound with a negative int value (e.g. -1) coming from client code, computed values, or overflow of arithmetic on the client side.

Common situations: Passing -1 as a sentinel for 'no TTL' (the correct sentinel is 0); subtraction/date math producing negative durations; configuration default TTL values entered as negative numbers.

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/53b7c49d31cb01cf. Report an issue: GitHub.