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
- Ensure every statement in the batch is an INSERT, UPDATE, or DELETE
- Move SELECT reads out of the batch and execute them separately
- Validate statement type before adding the prepared id to the batch (check the prepared metadata's kind)
- 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
- Type-check every statement before adding it to a batch
- Keep SELECT and DDL outside batch construction code paths
- Use driver batch builder APIs that reject non-DML statements
- Cover batch composition with tests that assert statement kinds
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
- Counter and non-counter mutations cannot exist in the same b
- Invalid empty consistency level
- There were %d markers(?) in CQL but %d bound variables
- Invalid null value of timestamp
- A TTL must be greater or equal to 0, but was <ttl>
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/162db3a86a700f8f.
Report an issue: GitHub.