{"record":{"id":"162db3a86a700f8f","repo":"apache/cassandra","slug":"invalid-statement-in-batch-only-update-insert-an","errorCode":null,"errorMessage":"Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed.","messagePattern":"Invalid statement in batch: only UPDATE, INSERT and DELETE statements are allowed\\.","errorType":"exception","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/transport/messages/BatchMessage.java","lineNumber":220,"sourceCode":"                    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.\");\n\n                statements.add((ModificationStatement) statement);\n            }\n\n            // Note: It's ok at this point to pass a bogus value for the number of bound terms in the BatchState ctor\n            // (and no value would be really correct, so we prefer passing a clearly wrong one).\n            BatchStatement batch = new BatchStatement(batchType, VariableSpecifications.empty(), statements, Attributes.none());\n\n            long queryTime = currentTimeMillis();\n            Message.Response response = handler.processBatch(batch, state, batchOptions, getCustomPayload(), requestTime);\n            if (queries != null)\n                QueryEvents.instance.notifyBatchSuccess(batchType, statements, queries, values, options, state, queryTime, response);\n            return response;\n        }\n        catch (Exception e)\n        {\n            QueryEvents.instance.notifyBatchFailure(prepared, batchType, queryOrIdList, values, options, state, e);\n            JVMStabilityInspector.inspectThrowable(e);","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/transport/messages/BatchMessage.java#L202-L238","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbatch.add(preparedSelectUsers); // SELECT in batch\n// after\nbatch.add(preparedInsertUser); // only INSERT/UPDATE/DELETE allowed\nResultSet rs = session.execute(preparedSelectUsers.bind());","handlingStrategy":"validation","validationCode":"for (Statement s : batch) {\n    if (!(s instanceof Batchable && isDml(s)))\n    throw new IllegalArgumentException(\"batch accepts only INSERT/UPDATE/DELETE: \" + s);\n}","typeGuard":"boolean isBatchableDml(Statement s) {\n    return s instanceof Insert || s instanceof Update || s instanceof Delete;\n}","tryCatchPattern":"try {\n    session.executeBatch(batch);\n} catch (InvalidRequestException e) {\n    if (e.getMessage().contains(\"only UPDATE, INSERT and DELETE\")) splitAndExecuteIndividually(batch);\n    else throw e;\n}","preventionTips":["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"],"tags":["cql","batch","invalid-request","unsupported-operation"],"backgroundTag":"unsupported-operation","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"}