{"record":{"id":"889e680bfeadfd19","repo":"apache/cassandra","slug":"invalid-schema-transformation-resultant-epoch-for","errorCode":null,"errorMessage":"Invalid schema transformation. Resultant epoch for table metadata of %s.%s (%d) is greater than for cluster metadata (%d)","messagePattern":"Invalid schema transformation\\. Resultant epoch for table metadata of (.+?)\\.(.+?) \\((.+?)\\) is greater than for cluster metadata \\((.+?)\\)","errorType":"exception","errorClass":"org.apache.cassandra.exceptions.InvalidRequestException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/transformations/AlterSchema.java","lineNumber":126,"sourceCode":"    public final Result execute(ClusterMetadata prev)\n    {\n        Keyspaces newKeyspaces;\n        try\n        {\n            // Applying the schema transformation may produce client warnings. If this is being executed by a follower\n            // of the cluster metadata log, there is no client or ClientState, so warning collection is a no-op.\n            // When a DDL statement is received from an actual client, the transformation is checked for validation\n            // and warnings are captured at that point, before being submitted to the CMS.\n            // If the coordinator is a CMS member, then this method will be called as part of committing to the metadata\n            // log. In this case, there is a connected client and associated ClientState, so to avoid duplicate warnings\n            // pause capture and resume after in applying the schema change.\n            schemaTransformation.enterExecution();\n            // Guard against an invalid SchemaTransformation supplying a TableMetadata with a future epoch\n            newKeyspaces = schemaTransformation.apply(prev);\n            newKeyspaces.forEach(ksm -> {\n               ksm.tables.forEach(tm -> {\n                   if (tm.epoch.isAfter(prev.nextEpoch()))\n                       throw new InvalidRequestException(String.format(\"Invalid schema transformation. \" +\n                                                                       \"Resultant epoch for table metadata of %s.%s (%d) \" +\n                                                                       \"is greater than for cluster metadata (%d)\",\n                                                                       ksm.name, tm.name, tm.epoch.getEpoch(),\n                                                                       prev.nextEpoch().getEpoch()));\n               });\n            });\n        }\n        catch (AlreadyExistsException t)\n        {\n            return new Rejected(ALREADY_EXISTS, t.getMessage());\n        }\n        catch (ConfigurationException t)\n        {\n            return new Rejected(CONFIG_ERROR, t.getMessage());\n        }\n        catch (InvalidRequestException t)\n        {\n            return new Rejected(INVALID, t.getMessage());","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/transformations/AlterSchema.java#L108-L144","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-written schema transformations copying TableMetadata from a future state; restoring table metadata blobs from a newer cluster; custom tooling that manipulates epochs.","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"],"exampleFix":"// before\ntable = TableMetadata.builder(...).build();\ntable = table.withEpoch(futureEpoch); // epoch > prev.nextEpoch()\n// after\nMetadata prev = ClusterMetadata.current();\ntable = table.withEpoch(prev.nextEpoch()); // derive epoch from current metadata","handlingStrategy":"validation","validationCode":"// before committing a custom schema transformation, check table epochs\nMetadata prev = ClusterMetadata.current();\nnewKeyspaces.forEach(ksm -> ksm.tables.forEach(tm -> {\n    if (tm.epoch.isAfter(prev.nextEpoch())) throw new IllegalStateException(\"table epoch too new: \" + tm.name);\n}));","typeGuard":null,"tryCatchPattern":"try { cms.commit(transformation); }\ncatch (InvalidRequestException e) {\n    if (e.getMessage().contains(\"Resultant epoch for table metadata\")) { /* fix transformation epochs */ }\n    else throw e;\n}","preventionTips":["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"],"tags":["schema","tcm","epoch"],"backgroundTag":"schema-validation-failed","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}