{"record":{"id":"f7ad734c38469d86","repo":"apache/iceberg","slug":"operation-expiresnapshots-is-not-supported-after-t","errorCode":null,"errorMessage":"Operation expireSnapshots is not supported after the table is serialized","messagePattern":"Operation expireSnapshots is not supported after the table is serialized","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/SerializableTable.java","lineNumber":422,"sourceCode":"\n  @Override\n  public DeleteFiles newDelete() {\n    throw new UnsupportedOperationException(errorMsg(\"newDelete\"));\n  }\n\n  @Override\n  public UpdateStatistics updateStatistics() {\n    throw new UnsupportedOperationException(errorMsg(\"updateStatistics\"));\n  }\n\n  @Override\n  public UpdatePartitionStatistics updatePartitionStatistics() {\n    throw new UnsupportedOperationException(errorMsg(\"updatePartitionStatistics\"));\n  }\n\n  @Override\n  public ExpireSnapshots expireSnapshots() {\n    throw new UnsupportedOperationException(errorMsg(\"expireSnapshots\"));\n  }\n\n  @Override\n  public ManageSnapshots manageSnapshots() {\n    throw new UnsupportedOperationException(errorMsg(\"manageSnapshots\"));\n  }\n\n  @Override\n  public Transaction newTransaction() {\n    throw new UnsupportedOperationException(errorMsg(\"newTransaction\"));\n  }\n\n  @Override\n  public StaticTableOperations operations() {\n    return (StaticTableOperations) ((BaseTable) lazyTable()).operations();\n  }\n\n  private String errorMsg(String operation) {","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/SerializableTable.java#L404-L440","documentation":"SerializableTable is a read-only, serializable snapshot of a table meant to be shipped to other nodes in a cluster (e.g. Spark executors). It deliberately rejects all mutation operations, including snapshot expiration, because writes on a deserialized copy cannot be committed back to the catalog. Calling expireSnapshots() on such a table throws this UnsupportedOperationException by design.","triggerScenarios":"Calling table.expireSnapshots() on a Table instance that is actually a SerializableTable — i.e. a table obtained after Java/Kryo serialization (typically inside a distributed task) or created via SerializableTable.copyOf(table).","commonSituations":"Running maintenance code (snapshot expiration, orphan file cleanup wrappers) inside a Spark/Flink executor where the table was broadcast or serialized with the task closure; holding a SerializableTable reference and assuming it behaves like a full BaseTable from a Catalog.","solutions":["Reload the table from its catalog on the node where the mutation runs (catalog.loadTable(location)) and call expireSnapshots() there, e.g. in the driver.","Use the convenience entry point HiveCatalog/SparkActions (e.g. Spark Actions expireSnapshots(table)) which handles reloading, instead of calling the Table API on a serialized copy.","Guard mutation code with an instanceof SerializableTable check and route it to a fresh catalog-loaded Table."],"exampleFix":"// before\nTable table = broadcastTable.value(); // SerializableTable on executor\ntable.expireSnapshots().olderThan(ts).execute();\n\n// after\nTable table = catalog.loadTable(tableLocation); // reload on the mutating node\ntable.expireSnapshots().olderThan(ts).execute();","handlingStrategy":"type-guard","validationCode":"if (table instanceof org.apache.iceberg.SerializableTable) {\n  table = catalog.loadTable(table.location()); // reload before expiring snapshots\n}","typeGuard":"boolean isMutable(Table t) {\n  return !(t instanceof org.apache.iceberg.SerializableTable);\n}","tryCatchPattern":"try {\n  table.expireSnapshots().olderThan(ts).execute();\n} catch (UnsupportedOperationException e) {\n  // fall back to catalog-loaded table\n  catalog.loadTable(table.location()).expireSnapshots().olderThan(ts).execute();\n}","preventionTips":["Only broadcast/serialize tables for read and scan work; keep all mutation on catalog-loaded tables.","Relocate maintenance operations (expire, rollback, transactions) to the driver.","Document helper methods to accept a Catalog plus location instead of a possibly-serialized Table."],"tags":["java","unsupported-operation","serialization","read-only-table"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}