{"record":{"id":"560eb4bf50b1b372","repo":"apache/cassandra","slug":"there-were-d-markers-in-cql-but-d-bound-varia-560eb4","errorCode":null,"errorMessage":"There were %d markers(?) in CQL but %d bound variables","messagePattern":"There were (.+?) markers\\(\\?\\) in CQL but (.+?) bound variables","errorType":"exception","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/transport/messages/BatchMessage.java","lineNumber":202,"sourceCode":"            {\n                Object query = queryOrIdList.get(i);\n                QueryHandler.Prepared p;\n                if (query instanceof String)\n                {\n                    p = QueryProcessor.parseAndPrepare((String) query,\n                                                       state.getClientState().cloneWithKeyspaceIfSet(options.getKeyspace()),\n                                                       false, false);\n                }\n                else\n                {\n                    p = handler.getPrepared((MD5Digest)query);\n                    if (null == p)\n                        throw new PreparedQueryNotFoundException((MD5Digest)query);\n                }\n\n                byte[][] queryValues = values.get(i);\n                if (queryValues.length != p.statement.getBindVariables().size())\n                    throw new InvalidRequestException(String.format(\"There were %d markers(?) in CQL but %d bound variables\",\n                                                                    p.statement.getBindVariables().size(),\n                                                                    queryValues.length));\n\n                prepared.add(p);\n            }\n\n            BatchQueryOptions batchOptions = BatchQueryOptions.withPerStatementVariables(options, values, queryOrIdList);\n            List<ModificationStatement> statements = new ArrayList<>(prepared.size());\n            List<String> queries = QueryEvents.instance.hasListeners() ? new ArrayList<>(prepared.size()) : null;\n            for (int i = 0; i < prepared.size(); i++)\n            {\n                CQLStatement statement = prepared.get(i).statement;\n                if (queries != null)\n                    queries.add(prepared.get(i).rawCQLStatement);\n                batchOptions.prepareStatement(i, statement.getBindVariables());\n\n                if (!(statement instanceof ModificationStatement))\n                    throw new InvalidRequestException(\"Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed.\");","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/transport/messages/BatchMessage.java#L184-L220","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nvalues.add(new byte[][]{ name, city });\n// after\n// statement has 3 markers: INSERT INTO users (id, name, city) VALUES (?,?,?)\nvalues.add(new byte[][]{ id, name, city });","handlingStrategy":"validation","validationCode":"PreparedStatement ps = session.prepare(cql);\nif (boundValues.length != ps.getVariableDefinitions().size())\n    throw new IllegalArgumentException(\"expected \" + ps.getVariableDefinitions().size() + \" values, got \" + boundValues.length);","typeGuard":"boolean valueCountMatches(PreparedStatement ps, Object[] values) {\n    return values != null && values.length == ps.getVariableDefinitions().size();\n}","tryCatchPattern":"try {\n    session.executeBatch(batch);\n} catch (InvalidRequestException e) {\n    if (e.getMessage().contains(\"markers(?)\")) rebindAndRetry(batch);\n    else throw e;\n}","preventionTips":["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"],"tags":["cql","batch","protocol","invalid-request"],"backgroundTag":"invalid-argument-value","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}