{"record":{"id":"30bc5cfd696899d5","repo":"apache/cassandra","slug":"global-ttl-on-the-batch-statement-is-not-supported","errorCode":null,"errorMessage":"Global TTL on the BATCH statement is not supported.","messagePattern":"Global TTL on the BATCH statement is not supported\\.","errorType":"validation","errorClass":"InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/BatchStatement.java","lineNumber":256,"sourceCode":"    @Override\n    public boolean eligibleAsPreparedStatement()\n    {\n        return true;\n    }\n\n    @Override\n    public void authorize(ClientState state) throws InvalidRequestException, UnauthorizedException\n    {\n        ModificationStatement.TableAuthorizationState authorizationState = new ModificationStatement.TableAuthorizationState();\n        for (ModificationStatement statement : statements)\n            statement.authorize(state, authorizationState);\n    }\n\n    // Validates a prepared batch statement without validating its nested statements.\n    public void validate() throws InvalidRequestException\n    {\n        if (attrs.isTimeToLiveSet())\n            throw new InvalidRequestException(\"Global TTL on the BATCH statement is not supported.\");\n\n        boolean timestampSet = attrs.isTimestampSet();\n        if (timestampSet)\n        {\n            if (hasConditions)\n                throw new InvalidRequestException(\"Cannot provide custom timestamp for conditional BATCH\");\n\n            if (isCounter())\n                throw new InvalidRequestException(\"Cannot provide custom timestamp for counter BATCH\");\n        }\n\n        boolean hasCounters = false;\n        boolean hasNonCounters = false;\n\n        boolean hasVirtualTables = false;\n        boolean hasRegularTables = false;\n\n        for (ModificationStatement statement : statements)","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java#L238-L274","documentation":"BATCH statements do not accept a batch-level USING TTL. BatchStatement.validate() rejects batches whose StatementAttributes have timeToLive set, because per-row TTLs must come from the individual INSERT/UPDATE statements.","triggerScenarios":"Executing BEGIN BATCH ... USING TTL 86400 ... APPLY BATCH, or preparing such a statement (validate is called from prepare and processBatch).","commonSituations":"Copy-pasting a single-statement INSERT ... USING TTL into a batch wrapper; ORM/query builders hoisting a global TTL onto the batch.","solutions":["Remove USING TTL from the BATCH header and add USING TTL to each individual statement inside the batch","If a uniform TTL is needed, generate the statements programmatically with the TTL applied per statement"],"exampleFix":"// before\nBEGIN BATCH INSERT INTO t ... USING TTL 86400 APPLY BATCH;\n// after\nBEGIN BATCH\n  INSERT INTO t ... USING TTL 86400;\nAPPLY BATCH;","handlingStrategy":"validation","validationCode":"if (batchCql.matches(\"(?is)BEGIN\\\\s+(UNLOGGED|COUNTER\\\\s+)?BATCH.*USING\\\\s+TTL\\\\b\"))\n    throw new IllegalArgumentException(\"USING TTL is not allowed on BATCH; apply per statement\");","typeGuard":null,"tryCatchPattern":"try { session.execute(batch); }\ncatch (InvalidQueryException e) {\n    if (e.getMessage().contains(\"Global TTL on the BATCH\")) { /* rewrite statements with per-row TTL */ }\n}","preventionTips":["Always put USING TTL on individual INSERT/UPDATE statements, never on the BATCH header","Encode this rule in query-builder tests"],"tags":["cql","batch","ttl"],"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"}