apache/iceberg · error · UnsupportedOperationException

Operation expireSnapshots is not supported after the table…

Error message

Operation expireSnapshots is not supported after the table is serialized

What it means

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.

Solutions

  1. 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.
  2. 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.
  3. Guard mutation code with an instanceof SerializableTable check and route it to a fresh catalog-loaded Table.

Example fix

// before
Table table = broadcastTable.value(); // SerializableTable on executor
table.expireSnapshots().olderThan(ts).execute();

// after
Table table = catalog.loadTable(tableLocation); // reload on the mutating node
table.expireSnapshots().olderThan(ts).execute();
Defensive patterns

Strategy: type-guard

Validate before calling

if (table instanceof org.apache.iceberg.SerializableTable) {
  table = catalog.loadTable(table.location()); // reload before expiring snapshots
}

Type guard

boolean isMutable(Table t) {
  return !(t instanceof org.apache.iceberg.SerializableTable);
}

Try / catch

try {
  table.expireSnapshots().olderThan(ts).execute();
} catch (UnsupportedOperationException e) {
  // fall back to catalog-loaded table
  catalog.loadTable(table.location()).expireSnapshots().olderThan(ts).execute();
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

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/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/f7ad734c38469d86. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/apache/iceberg/SerializableTable.java:422

  @Override
  public DeleteFiles newDelete() {
    throw new UnsupportedOperationException(errorMsg("newDelete"));
  }

  @Override
  public UpdateStatistics updateStatistics() {
    throw new UnsupportedOperationException(errorMsg("updateStatistics"));
  }

  @Override
  public UpdatePartitionStatistics updatePartitionStatistics() {
    throw new UnsupportedOperationException(errorMsg("updatePartitionStatistics"));
  }

  @Override
  public ExpireSnapshots expireSnapshots() {
    throw new UnsupportedOperationException(errorMsg("expireSnapshots"));
  }

  @Override
  public ManageSnapshots manageSnapshots() {
    throw new UnsupportedOperationException(errorMsg("manageSnapshots"));
  }

  @Override
  public Transaction newTransaction() {
    throw new UnsupportedOperationException(errorMsg("newTransaction"));
  }

  @Override
  public StaticTableOperations operations() {
    return (StaticTableOperations) ((BaseTable) lazyTable()).operations();
  }

  private String errorMsg(String operation) {

View on GitHub (pinned to 86d9c8fc54)