apache/cassandra · error · InvalidRequestException
Cannot set default_time_to_live on a table with counters
Error message
Cannot set default_time_to_live on a table with counters
What it means
Counter tables cannot have a positive default_time_to_live, because counter mutations cannot be TTL'd consistently. In AlterTableStatement.apply (line 692), after computing the altered TableParams, if the table isCounter() and params.defaultTimeToLive > 0 this InvalidRequest is thrown.
Source
Thrown at src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java:692
keyspaceName, tableName));
return next.unbuild().transactionalMigrationFrom(newMigrateFrom).build();
}
@Override
public boolean compatibleWith(ClusterMetadata metadata)
{
return metadata.directory.commonSerializationVersion.isAtLeast(Version.V0);
}
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{
attrs.validate();
TableParams params = attrs.asAlteredTableParams(table.params);
if (table.isCounter() && params.defaultTimeToLive > 0)
throw ire("Cannot set default_time_to_live on a table with counters");
if (!isEmpty(keyspace.views.forTable(table.id)) && params.gcGraceSeconds == 0)
{
throw ire("Cannot alter gc_grace_seconds of the base table of a " +
"materialized view to 0, since this value is used to TTL " +
"undelivered updates. Setting gc_grace_seconds too low might " +
"cause undelivered updates to expire " +
"before being replayed.");
}
if (keyspace.replicationStrategy.hasTransientReplicas()
&& params.readRepair != ReadRepairStrategy.NONE)
{
throw ire("read_repair must be set to 'NONE' for transiently replicated keyspaces");
}
if (!params.compression.isEnabled())
Guardrails.uncompressedTablesEnabled.ensureEnabled(state);View on GitHub (pinned to 88fd0f6a0e)
Solutions
- Do not set default_time_to_live on counter tables (keep it 0)
- Handle expiry of counters in the application layer (separate bucket/timestamp rows and periodic cleanup)
- Move the counter data into a regular table with TTL if time-based expiry is truly required
Example fix
// before ALTER TABLE metrics.hits WITH default_time_to_live = 86400; // after ALTER TABLE metrics.hits WITH comment = 'counters'; // no default_time_to_live
Defensive patterns
Strategy: validation
Validate before calling
TableMetadata t = Schema.instance.getTableMetadata(ks, table); if (t.isCounter() && attrs.defaultTimeToLive > 0) { /* reject or drop the default_time_to_live setting */ } Try / catch
try { session.execute(alterStmt); } catch (InvalidRequestException e) { if (e.getMessage().contains("Cannot set default_time_to_live on a table with counters")) { /* remove TTL from the counter table change */ } else throw e; } Prevention
- Never include default_time_to_live in generic WITH templates for counter tables
- Handle counter expiry at the application level
- Lint schema scripts for TTL options applied to counter tables
When it happens
Trigger: ALTER TABLE counter_table WITH default_time_to_live = 86400; (or any value > 0), either directly or via a default TTL inherited from a copied/parameterized schema script.
Common situations: Applying a generic WITH clause template to all tables including counter tables; assuming TTL works on counters like regular columns; migrating DDL from non-Cassandra setups.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- category %s not found in %s
- 'Get CIDR groups for IP' operation not supported by %s
- ACCESS TO DATACENTERS operations not supported by AllowAllNe
- frozen<> is only allowed on collections, tuples, and user-de
- Keyspace %s doesn't exist
AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10).
Data as JSON: /api/errors/82403af0f6ceabfe.
Report an issue: GitHub.