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
- Fix the custom SchemaTransformation so produced TableMetadata uses epochs derived from the current metadata (not carried-over future epochs)
- Rebuild the transformation using the standard DSL (create/alter/drop table) instead of raw TableMetadata construction
- 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
- Derive TableMetadata epochs from ClusterMetadata.current(), never reuse epochs from other clusters
- Use the standard schema DSL instead of raw TableMetadata construction in custom transformations
- Do not import schema blobs from backups of clusters with higher epochs
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
- Could not catch up to epoch
- Previous epoch indicates that the has not been initialized…
- Still behind after fetching log from CMS
- ACCESS TO DATACENTERS operations not supported by…
- Addresses differ: !=
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)