apache/cassandra · error · InvalidRequestException

Invalid statement in batch: only UPDATE, INSERT and DELETE s

Error message

Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed.

What it means

Only DML statements (UPDATE, INSERT, DELETE) may appear inside a batch. If a prepared statement resolves to something else (SELECT, USE, DDL like CREATE TABLE, etc.), BatchMessage.execute throws InvalidRequestException because batching non-modification statements is not supported by the protocol or the storage engine.

Source

Thrown at src/java/org/apache/cassandra/transport/messages/BatchMessage.java:220

                    throw new InvalidRequestException(String.format("There were %d markers(?) in CQL but %d bound variables",
                                                                    p.statement.getBindVariables().size(),
                                                                    queryValues.length));

                prepared.add(p);
            }

            BatchQueryOptions batchOptions = BatchQueryOptions.withPerStatementVariables(options, values, queryOrIdList);
            List<ModificationStatement> statements = new ArrayList<>(prepared.size());
            List<String> queries = QueryEvents.instance.hasListeners() ? new ArrayList<>(prepared.size()) : null;
            for (int i = 0; i < prepared.size(); i++)
            {
                CQLStatement statement = prepared.get(i).statement;
                if (queries != null)
                    queries.add(prepared.get(i).rawCQLStatement);
                batchOptions.prepareStatement(i, statement.getBindVariables());

                if (!(statement instanceof ModificationStatement))
                    throw new InvalidRequestException("Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed.");

                statements.add((ModificationStatement) statement);
            }

            // Note: It's ok at this point to pass a bogus value for the number of bound terms in the BatchState ctor
            // (and no value would be really correct, so we prefer passing a clearly wrong one).
            BatchStatement batch = new BatchStatement(batchType, VariableSpecifications.empty(), statements, Attributes.none());

            long queryTime = currentTimeMillis();
            Message.Response response = handler.processBatch(batch, state, batchOptions, getCustomPayload(), requestTime);
            if (queries != null)
                QueryEvents.instance.notifyBatchSuccess(batchType, statements, queries, values, options, state, queryTime, response);
            return response;
        }
        catch (Exception e)
        {
            QueryEvents.instance.notifyBatchFailure(prepared, batchType, queryOrIdList, values, options, state, e);
            JVMStabilityInspector.inspectThrowable(e);

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Ensure every statement in the batch is an INSERT, UPDATE, or DELETE
  2. Move SELECT reads out of the batch and execute them separately
  3. Validate statement type before adding the prepared id to the batch (check the prepared metadata's kind)
  4. Re-check id-to-statement mappings if a driver cache may map an id to the wrong statement

Example fix

// before
batch.add(preparedSelectUsers); // SELECT in batch
// after
batch.add(preparedInsertUser); // only INSERT/UPDATE/DELETE allowed
ResultSet rs = session.execute(preparedSelectUsers.bind());
Defensive patterns

Strategy: validation

Validate before calling

for (Statement s : batch) {
    if (!(s instanceof Batchable && isDml(s)))
    throw new IllegalArgumentException("batch accepts only INSERT/UPDATE/DELETE: " + s);
}

Type guard

boolean isBatchableDml(Statement s) {
    return s instanceof Insert || s instanceof Update || s instanceof Delete;
}

Try / catch

try {
    session.executeBatch(batch);
} catch (InvalidRequestException e) {
    if (e.getMessage().contains("only UPDATE, INSERT and DELETE")) splitAndExecuteIndividually(batch);
    else throw e;
}

Prevention

When it happens

Trigger: Sending a BatchMessage containing a prepared statement id whose target is not a ModificationStatement — e.g. preparing a SELECT and putting its id in the batch, or a USE/TRUNCATE/DDL statement in a batch.

Common situations: Misordered prepared-id maps in custom drivers, application code that dynamically builds batches and accidentally includes a SELECT, template engines inserting conditional statements into batches.

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/162db3a86a700f8f. Report an issue: GitHub.