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

  1. 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
  2. Re-prepare the statement if the value count changed — the prepared id may point to a stale statement
  3. Log queryValues.length vs the prepared metadata's variable count to identify which batch entry mismatches
  4. 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

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


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/560eb4bf50b1b372. Report an issue: GitHub.