apache/cassandra · error · InvalidRequestException
There were %d markers(?) in CQL but %d bound variables
Error message
There were %d markers(?) in CQL but %d bound variables
What it means
In a BATCH message, each query or prepared-statement id is sent together with its bound values. Cassandra compares the number of supplied values against the number of bind markers ("?") in the underlying statement and throws InvalidRequestException when they differ, because the statement cannot be executed with a mismatched value set.
Source
Thrown at src/java/org/apache/cassandra/transport/messages/BatchMessage.java:202
{
Object query = queryOrIdList.get(i);
QueryHandler.Prepared p;
if (query instanceof String)
{
p = QueryProcessor.parseAndPrepare((String) query,
state.getClientState().cloneWithKeyspaceIfSet(options.getKeyspace()),
false, false);
}
else
{
p = handler.getPrepared((MD5Digest)query);
if (null == p)
throw new PreparedQueryNotFoundException((MD5Digest)query);
}
byte[][] queryValues = values.get(i);
if (queryValues.length != p.statement.getBindVariables().size())
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.");View on GitHub (pinned to 88fd0f6a0e)
Solutions
- Count the statement's bind variables (e.g. from the prepared statement metadata the driver already returned) and send exactly that many values per batch entry
- Re-prepare the statement if the value count changed — the prepared id may point to a stale statement
- Log queryValues.length vs the prepared metadata's variable count to identify which batch entry mismatches
- If using a driver, let it compute bound values from the prepared statement instead of hand-building byte[][] arrays
Example fix
// before
values.add(new byte[][]{ name, city });
// after
// statement has 3 markers: INSERT INTO users (id, name, city) VALUES (?,?,?)
values.add(new byte[][]{ id, name, city }); Defensive patterns
Strategy: validation
Validate before calling
PreparedStatement ps = session.prepare(cql);
if (boundValues.length != ps.getVariableDefinitions().size())
throw new IllegalArgumentException("expected " + ps.getVariableDefinitions().size() + " values, got " + boundValues.length); Type guard
boolean valueCountMatches(PreparedStatement ps, Object[] values) {
return values != null && values.length == ps.getVariableDefinitions().size();
} Try / catch
try {
session.executeBatch(batch);
} catch (InvalidRequestException e) {
if (e.getMessage().contains("markers(?)")) rebindAndRetry(batch);
else throw e;
} Prevention
- Bind values via the driver's PreparedStatement API instead of building raw byte arrays
- Assert value count equals bind-marker count in unit tests for every batched statement
- Rebuild value arrays whenever the CQL string changes
- Enable driver query warnings/logs to surface mismatched binds early
When it happens
Trigger: Sending a BatchMessage where queryValues.length for entry i does not equal p.statement.getBindVariables().size() — e.g. mixing named/positional values, or reusing a prepared statement whose id resolves to a different statement than the one that was bound.
Common situations: Driver-side statement rebinding after a prepared statement was re-prepared (id reuse after host restart), application bugs where some positional values are omitted, JSON/template-generated batch queries with variable-length value lists.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Counter and non-counter mutations cannot exist in the same b
- Invalid empty consistency level
- Invalid statement in batch: only UPDATE, INSERT and DELETE s
- The page size cannot be 0
- The page size cannot be 0
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/560eb4bf50b1b372.
Report an issue: GitHub.