apache/cassandra · error · InvalidRequestException

Conditional BATCH statements cannot include mutations for vi

Error message

Conditional BATCH statements cannot include mutations for virtual tables

What it means

InvalidRequestException from BatchStatement.validate: the batch carries conditions (IF ... — a conditional/LWT batch) and includes mutations against virtual tables. Virtual tables cannot participate in CAS/conditional logic, so their presence alongside conditions is rejected during batch validation (companion guards reject counters in logged batches and mixed counter/non-counter batches).

Source

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

            throw new InvalidRequestException("Cannot provide custom timestamp for a BATCH containing counters");

        if (isCounter() && hasNonCounters)
            throw new InvalidRequestException("Cannot include non-counter statement in a counter batch");

        if (hasCounters && hasNonCounters)
            throw new InvalidRequestException("Counter and non-counter mutations cannot exist in the same batch");

        if (isLogged() && hasCounters)
            throw new InvalidRequestException("Cannot include a counter statement in a logged batch");

        if (isLogged() && hasVirtualTables)
            throw new InvalidRequestException("Cannot include a virtual table statement in a logged batch");

        if (hasVirtualTables && hasRegularTables)
            throw new InvalidRequestException("Mutations for virtual and regular tables cannot exist in the same batch");

        if (hasConditions && hasVirtualTables)
            throw new InvalidRequestException("Conditional BATCH statements cannot include mutations for virtual tables");

        if (hasConditions)
        {
            String ksName = null;
            String cfName = null;
            for (ModificationStatement stmt : statements)
            {
                if (ksName != null && (!stmt.keyspace().equals(ksName) || !stmt.table().equals(cfName)))
                    throw new InvalidRequestException("Batch with conditions cannot span multiple tables: " + stmt.source);
                ksName = stmt.keyspace();
                cfName = stmt.table();
            }
        }
    }

    private boolean isCounter()
    {
        return type == Type.COUNTER;

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Remove virtual table statements from conditional batches — LWT applies only to regular tables
  2. Move the conditional logic to regular table mutations only
  3. If conditional behavior on virtual data is needed, implement it application-side

Example fix

// before
BEGIN BATCH UPDATE ks.t SET v = 2 WHERE k = 1 IF v = 1;
             UPDATE system_views.x SET a = 1 WHERE b = 2; APPLY BATCH;
// after
BEGIN BATCH UPDATE ks.t SET v = 2 WHERE k = 1 IF v = 1; APPLY BATCH;
Defensive patterns

Strategy: validation

Validate before calling

if (batchHasConditions && stmts.stream().anyMatch(s -> s.getTable().isVirtual()))
    throw new IllegalArgumentException("LWT batches cannot include virtual tables");

Try / catch

catch (InvalidRequestException e) { if (e.getMessage().contains("Conditional BATCH") && e.getMessage().contains("virtual")) { dropVirtualStatementsAndRetry(); } else throw e; }

Prevention

When it happens

Trigger: 'BEGIN BATCH ... UPDATE system_views.v ... IF EXISTS; APPLY BATCH' — applying LWT conditions to virtual table mutations.

Common situations: Developers assuming virtual tables support compare-and-set like regular tables; generated conditional batches including virtual tables.

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/9ba2c04b44040105. Report an issue: GitHub.