{"record":{"id":"f89ffdea25c430dd","repo":"apache/cassandra","slug":"counter-mutations-and-trigger-mutations-cannot-be","errorCode":null,"errorMessage":"Counter mutations and trigger mutations cannot be applied together atomically.","messagePattern":"Counter mutations and trigger mutations cannot be applied together atomically\\.","errorType":"exception","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/triggers/TriggerExecutor.java","lineNumber":159,"sourceCode":"            for (PartitionUpdate upd : mutation.getPartitionUpdates())\n            {\n                List<Mutation> augmentations = executeInternal(upd);\n                if (augmentations == null || augmentations.isEmpty())\n                    continue;\n\n                validate(augmentations);\n\n                if (augmentedMutations == null)\n                    augmentedMutations = new LinkedList<>();\n                augmentedMutations.addAll(augmentations);\n            }\n        }\n\n        if (augmentedMutations == null)\n            return null;\n\n        if (hasCounters)\n            throw new InvalidRequestException(\"Counter mutations and trigger mutations cannot be applied together atomically.\");\n\n        @SuppressWarnings(\"unchecked\")\n        Collection<Mutation> originalMutations = (Collection<Mutation>) mutations;\n\n        return mergeMutations(Iterables.concat(originalMutations, augmentedMutations));\n    }\n\n    private List<Mutation> mergeMutations(Iterable<Mutation> mutations)\n    {\n        ListMultimap<Pair<String, ByteBuffer>, Mutation> groupedMutations = ArrayListMultimap.create();\n\n        for (Mutation mutation : mutations)\n        {\n            Pair<String, ByteBuffer> key = Pair.create(mutation.getKeyspaceName(), mutation.key().getKey());\n            groupedMutations.put(key, mutation);\n        }\n\n        List<Mutation> merged = new ArrayList<>(groupedMutations.size());","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/triggers/TriggerExecutor.java#L141-L177","documentation":"TriggerExecutor executes user-defined triggers that can augment the original mutations with additional ones. If the original statement contains counter mutations (PartitionUpdates with counters) and the trigger also produced augmented mutations, the two cannot be applied atomically, so the executor rejects the request with InvalidRequestException instead of applying them partially.","triggerScenarios":"An INSERT/UPDATE/BATCH touching counter columns on a table that also has a trigger defined, where the trigger's execute() returns non-null augmented mutations.","commonSituations":"Adding a trigger to an existing counter table; running a batch mixing counter and non-counter writes on a triggered table; migrating an application that used triggers for audit writes onto counter tables.","solutions":["Remove the trigger from the counter table (DROP TRIGGER)","Split the operation so counter writes and trigger-augmented non-counter writes are issued as separate statements","Redesign the trigger to only apply to non-counter tables, or replace the trigger with application-level writes"],"exampleFix":"// before\n// trigger on counter table + counter update\nUPDATE counters SET votes = votes + 1 WHERE pk = 1; // rejected\n// after\nDROP TRIGGER audit_trg ON counters;\nUPDATE counters SET votes = votes + 1 WHERE pk = 1;","handlingStrategy":"try-catch","validationCode":"// before executing: check table has counters and a trigger\nif (tableMeta.hasCounterColumns() && tableMeta.getTriggers().size() > 0) throw new IllegalStateException('Trigger on counter table is not allowed');","typeGuard":null,"tryCatchPattern":"catch InvalidRequestException with message containing 'Counter mutations and trigger mutations' and surface a config error to the operator","preventionTips":["Never create triggers on counter tables","Audit schema for triggers with `SELECT * FROM system_schema.triggers;`","Split counter and non-counter writes into separate statements"],"tags":["triggers","counters","invalid-request","atomicity"],"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-17T15:17:12.973Z"}