apache/cassandra · error · InvalidRequestException

Counter columns cannot be accessed within a transaction;

Error message

Counter columns cannot be accessed within a transaction; %s statement %s

What it means

Counter tables cannot be read (or written) inside Accord transactions; the transaction engine does not support counter columns. For SELECTs, TransactionStatement.validate(SelectStatement) throws when prepared.table.isCounter().

Solutions

  1. Move counter reads/writes out of the transaction
  2. Migrate to non-counter columns (e.g., plain longs with CAS or a txn-managed value) if atomic combined semantics are needed
  3. Restructure the schema so counters and transactional data use separate tables and separate code paths

Example fix

// before
BEGIN TRANSACTION SELECT c FROM counter_t WHERE pk = 1; COMMIT;
// after
SELECT c FROM counter_t WHERE pk = 1; // outside any transaction
Defensive patterns

Strategy: validation

Validate before calling

TableMetadata t = cluster.getMetadata().getKeyspace(ks).getTable(table); if (t.getOptions().getCounter()) throw new IllegalStateException("counters cannot be used in transactions: " + table);

Try / catch

try { session.execute(txnCql); } catch (InvalidRequestException e) { if (e.getMessage().contains("Counter columns cannot be accessed within a transaction")) { /* route to non-transactional path */ } else throw e; }

Prevention

When it happens

Trigger: A SELECT within BEGIN TRANSACTION ... COMMIT targets a COUNTER table.

Common situations: Mixing counter increment logic into a transaction; retrofitting a legacy counter table into Accord-based workflows.

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


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/39d35848d981f5c5. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java:710

        return false;
    }

    private static void validate(SelectStatement.RawStatement select)
    {
        if (select.parameters.orderings != null && !select.parameters.orderings.isEmpty())
            throw invalidRequest(NO_ORDER_BY_IN_TXNS_MESSAGE, "SELECT", select.source);
        if (select.parameters.groups != null && !select.parameters.groups.isEmpty())
            throw invalidRequest(NO_GROUP_BY_IN_TXNS_MESSAGE, "SELECT", select.source);
    }

    private static void validate(SelectStatement prepared)
    {
        if (!prepared.table.isAccordEnabled())
            throw invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_MESSAGE, "SELECT", prepared.source);
        if (prepared.table.params.pendingDrop)
            throw invalidRequest(TRANSACTIONS_DISABLED_ON_TABLE_BEING_DROPPED_MESSAGE, "SELECT", prepared.source);
        if (prepared.table.isCounter())
            throw invalidRequest(NO_COUNTERS_IN_TXNS_MESSAGE, "SELECT", prepared.source);
        if (prepared.hasAggregation())
            throw invalidRequest(NO_AGGREGATION_IN_TXNS_MESSAGE, "SELECT", prepared.source);

        // when "LIMIT ?" this check can't be performed, so need to do again once the options are known
        if (prepared.getRestrictions().keyIsInRelation())
            checkTrue(prepared.isLimitMarker() || prepared.getLimit(null) == DataLimits.NO_LIMIT, NO_PARTITION_IN_CLAUSE_WITH_LIMIT, "SELECT", prepared.source);
    }

    public static class Parsed extends QualifiedStatement.Composite
    {
        private final List<SelectStatement.RawStatement> assignments;
        private final SelectStatement.RawStatement select;
        private final List<RowDataReference.Raw> returning;
        private final List<ModificationStatement.Parsed> updates;
        private final List<ConditionStatement.Raw> conditions;
        private final List<RowDataReference.Raw> dataReferences;

        public Parsed(List<SelectStatement.RawStatement> assignments,

View on GitHub (pinned to 88fd0f6a0e)