apache/cassandra · error · org.apache.cassandra.exceptions.InvalidRequestException

Invalid schema transformation. Resultant epoch for table…

Error message

Invalid schema transformation. Resultant epoch for table metadata of %s.%s (%d) is greater than for cluster metadata (%d)

What it means

AlterSchema.execute guards against a custom SchemaTransformation that returns TableMetadata whose epoch is after the cluster's nextEpoch. Table epochs must never be ahead of cluster metadata epochs; a transformation violating this would create inconsistent epoch ordering, so InvalidRequestException is thrown before the change is committed.

Solutions

  1. Fix the custom SchemaTransformation so produced TableMetadata uses epochs derived from the current metadata (not carried-over future epochs)
  2. Rebuild the transformation using the standard DSL (create/alter/drop table) instead of raw TableMetadata construction
  3. Do not import TableMetadata from backups of clusters with higher epochs; recreate the schema instead

Example fix

// before
table = TableMetadata.builder(...).build();
table = table.withEpoch(futureEpoch); // epoch > prev.nextEpoch()
// after
Metadata prev = ClusterMetadata.current();
table = table.withEpoch(prev.nextEpoch()); // derive epoch from current metadata
Defensive patterns

Strategy: validation

Validate before calling

// before committing a custom schema transformation, check table epochs
Metadata prev = ClusterMetadata.current();
newKeyspaces.forEach(ksm -> ksm.tables.forEach(tm -> {
    if (tm.epoch.isAfter(prev.nextEpoch())) throw new IllegalStateException("table epoch too new: " + tm.name);
}));

Try / catch

try { cms.commit(transformation); }
catch (InvalidRequestException e) {
    if (e.getMessage().contains("Resultant epoch for table metadata")) { /* fix transformation epochs */ }
    else throw e;
}

Prevention

When it happens

Trigger: Supplying a custom SchemaTransformation (programmatic/unsafe path) that fabricates or preserves a TableMetadata with an epoch greater than prev.nextEpoch(), then committing it through AlterSchema.

Common situations: Hand-written schema transformations copying TableMetadata from a future state; restoring table metadata blobs from a newer cluster; custom tooling that manipulates epochs.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/java/org/apache/cassandra/tcm/transformations/AlterSchema.java:126

    public final Result execute(ClusterMetadata prev)
    {
        Keyspaces newKeyspaces;
        try
        {
            // Applying the schema transformation may produce client warnings. If this is being executed by a follower
            // of the cluster metadata log, there is no client or ClientState, so warning collection is a no-op.
            // When a DDL statement is received from an actual client, the transformation is checked for validation
            // and warnings are captured at that point, before being submitted to the CMS.
            // If the coordinator is a CMS member, then this method will be called as part of committing to the metadata
            // log. In this case, there is a connected client and associated ClientState, so to avoid duplicate warnings
            // pause capture and resume after in applying the schema change.
            schemaTransformation.enterExecution();
            // Guard against an invalid SchemaTransformation supplying a TableMetadata with a future epoch
            newKeyspaces = schemaTransformation.apply(prev);
            newKeyspaces.forEach(ksm -> {
               ksm.tables.forEach(tm -> {
                   if (tm.epoch.isAfter(prev.nextEpoch()))
                       throw new InvalidRequestException(String.format("Invalid schema transformation. " +
                                                                       "Resultant epoch for table metadata of %s.%s (%d) " +
                                                                       "is greater than for cluster metadata (%d)",
                                                                       ksm.name, tm.name, tm.epoch.getEpoch(),
                                                                       prev.nextEpoch().getEpoch()));
               });
            });
        }
        catch (AlreadyExistsException t)
        {
            return new Rejected(ALREADY_EXISTS, t.getMessage());
        }
        catch (ConfigurationException t)
        {
            return new Rejected(CONFIG_ERROR, t.getMessage());
        }
        catch (InvalidRequestException t)
        {
            return new Rejected(INVALID, t.getMessage());

View on GitHub (pinned to 88fd0f6a0e)