apache/cassandra · error · InvalidRequestException

Global TTL on the BATCH statement is not supported.

Error message

Global TTL on the BATCH statement is not supported.

What it means

BATCH statements do not accept a batch-level USING TTL. BatchStatement.validate() rejects batches whose StatementAttributes have timeToLive set, because per-row TTLs must come from the individual INSERT/UPDATE statements.

Source

Thrown at src/java/org/apache/cassandra/cql3/statements/BatchStatement.java:256

    @Override
    public boolean eligibleAsPreparedStatement()
    {
        return true;
    }

    @Override
    public void authorize(ClientState state) throws InvalidRequestException, UnauthorizedException
    {
        ModificationStatement.TableAuthorizationState authorizationState = new ModificationStatement.TableAuthorizationState();
        for (ModificationStatement statement : statements)
            statement.authorize(state, authorizationState);
    }

    // Validates a prepared batch statement without validating its nested statements.
    public void validate() throws InvalidRequestException
    {
        if (attrs.isTimeToLiveSet())
            throw new InvalidRequestException("Global TTL on the BATCH statement is not supported.");

        boolean timestampSet = attrs.isTimestampSet();
        if (timestampSet)
        {
            if (hasConditions)
                throw new InvalidRequestException("Cannot provide custom timestamp for conditional BATCH");

            if (isCounter())
                throw new InvalidRequestException("Cannot provide custom timestamp for counter BATCH");
        }

        boolean hasCounters = false;
        boolean hasNonCounters = false;

        boolean hasVirtualTables = false;
        boolean hasRegularTables = false;

        for (ModificationStatement statement : statements)

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Remove USING TTL from the BATCH header and add USING TTL to each individual statement inside the batch
  2. If a uniform TTL is needed, generate the statements programmatically with the TTL applied per statement

Example fix

// before
BEGIN BATCH INSERT INTO t ... USING TTL 86400 APPLY BATCH;
// after
BEGIN BATCH
  INSERT INTO t ... USING TTL 86400;
APPLY BATCH;
Defensive patterns

Strategy: validation

Validate before calling

if (batchCql.matches("(?is)BEGIN\\s+(UNLOGGED|COUNTER\\s+)?BATCH.*USING\\s+TTL\\b"))
    throw new IllegalArgumentException("USING TTL is not allowed on BATCH; apply per statement");

Try / catch

try { session.execute(batch); }
catch (InvalidQueryException e) {
    if (e.getMessage().contains("Global TTL on the BATCH")) { /* rewrite statements with per-row TTL */ }
}

Prevention

When it happens

Trigger: Executing BEGIN BATCH ... USING TTL 86400 ... APPLY BATCH, or preparing such a statement (validate is called from prepare and processBatch).

Common situations: Copy-pasting a single-statement INSERT ... USING TTL into a batch wrapper; ORM/query builders hoisting a global TTL onto the batch.

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/30bc5cfd696899d5. Report an issue: GitHub.