apache/cassandra · error · InvalidRequestException
Cannot include a counter statement in a logged batch
Error message
Cannot include a counter statement in a logged batch
What it means
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.
Source
Thrown at src/java/org/apache/cassandra/cql3/statements/BatchStatement.java:300
hasNonCounters = true;
if (statement.isVirtual())
hasVirtualTables = true;
else
hasRegularTables = true;
}
if (timestampSet && hasCounters)
throw new InvalidRequestException("Cannot provide custom timestamp for a BATCH containing counters");
if (isCounter() && hasNonCounters)
throw new InvalidRequestException("Cannot include non-counter statement in a counter batch");
if (hasCounters && hasNonCounters)
throw new InvalidRequestException("Counter and non-counter mutations cannot exist in the same batch");
if (isLogged() && hasCounters)
throw new InvalidRequestException("Cannot include a counter statement in a logged batch");
if (isLogged() && hasVirtualTables)
throw new InvalidRequestException("Cannot include a virtual table statement in a logged batch");
if (hasVirtualTables && hasRegularTables)
throw new InvalidRequestException("Mutations for virtual and regular tables cannot exist in the same batch");
if (hasConditions && hasVirtualTables)
throw new InvalidRequestException("Conditional BATCH statements cannot include mutations for virtual tables");
if (hasConditions)
{
String ksName = null;
String cfName = null;
for (ModificationStatement stmt : statements)
{
if (ksName != null && (!stmt.keyspace().equals(ksName) || !stmt.table().equals(cfName)))
throw new InvalidRequestException("Batch with conditions cannot span multiple tables: " + stmt.source);View on GitHub (pinned to 88fd0f6a0e)
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
Example fix
// before BEGIN BATCH UPDATE stats SET hits = hits + 1 WHERE id = 1; APPLY BATCH; // after BEGIN UNLOGGED BATCH UPDATE stats SET hits = hits + 1 WHERE id = 1; APPLY BATCH;
Defensive patterns
Strategy: validation
Validate before calling
if (statements.stream().anyMatch(s -> isCounterTable(s.getTable())) && batchType == BatchType.LOGGED)
batchType = BatchType.UNLOGGED; // counters require unlogged batches Try / catch
catch (InvalidRequestException e) { if (e.getMessage().contains("logged batch")) { retryAsUnlogged(); } else throw e; } Prevention
- 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
When it happens
Trigger: 'BEGIN BATCH UPDATE counter_table SET c = c + 1 ...; APPLY BATCH' — i.e. a counter mutation inside a batch that is not declared UNLOGGED.
Common situations: Developers forgetting the counter/logged-batch rule; frameworks generating plain BEGIN BATCH for all writes; schema changed to counters after batch code was written.
Related errors
- Counter and non-counter mutations cannot exist in the same b
- Cannot include a virtual table statement in a logged batch
- LIST PERMISSIONS operation is not supported by AllowAllAutho
- Counters are not allowed inside collections:
- Counters are not allowed inside tuples
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/44d7aff5efaa6e43.
Report an issue: GitHub.