{"record":{"id":"44d7aff5efaa6e43","repo":"apache/cassandra","slug":"cannot-include-a-counter-statement-in-a-logged-bat","errorCode":null,"errorMessage":"Cannot include a counter statement in a logged batch","messagePattern":"Cannot include a counter statement in a logged batch","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/BatchStatement.java","lineNumber":300,"sourceCode":"                hasNonCounters = true;\n\n            if (statement.isVirtual())\n                hasVirtualTables = true;\n            else\n                hasRegularTables = true;\n        }\n\n        if (timestampSet && hasCounters)\n            throw new InvalidRequestException(\"Cannot provide custom timestamp for a BATCH containing counters\");\n\n        if (isCounter() && hasNonCounters)\n            throw new InvalidRequestException(\"Cannot include non-counter statement in a counter batch\");\n\n        if (hasCounters && hasNonCounters)\n            throw new InvalidRequestException(\"Counter and non-counter mutations cannot exist in the same batch\");\n\n        if (isLogged() && hasCounters)\n            throw new InvalidRequestException(\"Cannot include a counter statement in a logged batch\");\n\n        if (isLogged() && hasVirtualTables)\n            throw new InvalidRequestException(\"Cannot include a virtual table statement in a logged batch\");\n\n        if (hasVirtualTables && hasRegularTables)\n            throw new InvalidRequestException(\"Mutations for virtual and regular tables cannot exist in the same batch\");\n\n        if (hasConditions && hasVirtualTables)\n            throw new InvalidRequestException(\"Conditional BATCH statements cannot include mutations for virtual tables\");\n\n        if (hasConditions)\n        {\n            String ksName = null;\n            String cfName = null;\n            for (ModificationStatement stmt : statements)\n            {\n                if (ksName != null && (!stmt.keyspace().equals(ksName) || !stmt.table().equals(cfName)))\n                    throw new InvalidRequestException(\"Batch with conditions cannot span multiple tables: \" + stmt.source);","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java#L282-L318","documentation":"Counter mutations are not safe in logged batches: logged batches write to the batchlog for atomicity, but counter updates are not idempotent and cannot be replayed safely. validate() therefore rejects any logged batch (default BATCH without UNLOGGED flag) that contains a counter statement.","triggerScenarios":"'BEGIN BATCH UPDATE counter_table SET c = c + 1 ...; APPLY BATCH' — i.e. a counter mutation inside a batch that is not declared UNLOGGED.","commonSituations":"Developers forgetting the counter/logged-batch rule; frameworks generating plain BEGIN BATCH for all writes; schema changed to counters after batch code was written.","solutions":["Use BEGIN UNLOGGED BATCH for batches containing counter statements","Prefer executing counter mutations outside of batches, or in a dedicated UNLOGGED batch containing only counters","Reconsider using counters if atomic multi-write semantics are required — counters cannot be batched atomically"],"exampleFix":"// before\nBEGIN BATCH UPDATE stats SET hits = hits + 1 WHERE id = 1; APPLY BATCH;\n// after\nBEGIN UNLOGGED BATCH UPDATE stats SET hits = hits + 1 WHERE id = 1; APPLY BATCH;","handlingStrategy":"validation","validationCode":"if (statements.stream().anyMatch(s -> isCounterTable(s.getTable())) && batchType == BatchType.LOGGED)\n    batchType = BatchType.UNLOGGED; // counters require unlogged batches","typeGuard":null,"tryCatchPattern":"catch (InvalidRequestException e) { if (e.getMessage().contains(\"logged batch\")) { retryAsUnlogged(); } else throw e; }","preventionTips":["Default any batch that includes counter tables to UNLOGGED","Remember: logged batches buy atomicity/isolation that counters cannot honor","Document in team guidelines: counter writes go in UNLOGGED batches only"],"tags":["cql","batch","counter","logged-batch"],"backgroundTag":"mutually-exclusive-options","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"}