apache/iceberg · error · UnsupportedOperationException

%s doesn't implement cleanupLevel

Error message

%s doesn't implement cleanupLevel

What it means

ExpireSnapshots.cleanupLevel is a default interface method that throws unless the concrete implementation supports choosing a cleanup level. The default exists so implementations predating CleanupLevel remain source-compatible; calling it on such an implementation surfaces this error naming the implementing class.

Source

Thrown at api/src/main/java/org/apache/iceberg/ExpireSnapshots.java:150

  ExpireSnapshots cleanExpiredFiles(boolean clean);

  /**
   * Configures the cleanup level for expired files.
   *
   * <p>This method provides fine-grained control over which files are cleaned up during snapshot
   * expiration.
   *
   * <p>Consider {@link CleanupLevel#METADATA_ONLY} when data files are shared across tables or when
   * using procedures like add-files that may reference the same data files.
   *
   * <p>Consider {@link CleanupLevel#NONE} when data and metadata files may be more efficiently
   * removed using a distributed framework through the actions API.
   *
   * @param level the cleanup level to use for expired snapshots
   * @return this for method chaining
   */
  default ExpireSnapshots cleanupLevel(CleanupLevel level) {
    throw new UnsupportedOperationException(
        this.getClass().getName() + " doesn't implement cleanupLevel");
  }

  /**
   * Enable cleaning up unused metadata, such as partition specs, schemas, etc.
   *
   * @param clean remove unused partition specs, schemas, or other metadata when true
   * @return this for method chaining
   */
  default ExpireSnapshots cleanExpiredMetadata(boolean clean) {
    throw new UnsupportedOperationException(
        this.getClass().getName() + " doesn't implement cleanExpiredMetadata");
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Upgrade the implementation (Spark/Flink actions or custom class) to one that overrides cleanupLevel
  2. Remove the cleanupLevel(...) call and rely on the implementation's default cleanup behavior
  3. If you own the implementation, implement cleanupLevel to store and honor the CleanupLevel

Example fix

// before
expireSnapshots.cleanupLevel(CleanupLevel.LOCAL).execute();
// after
if (supportsCleanupLevel) {
  expireSnapshots.cleanupLevel(CleanupLevel.LOCAL).execute();
} else {
  expireSnapshots.execute();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// no pre-call capability flag exists; feature-gate by implementation type
boolean supportsCleanupLevel = !(expireSnapshots.getClass().getName().startsWith("legacy."));

Try / catch

try {
  expireSnapshots.cleanupLevel(CleanupLevel.LOCAL);
} catch (UnsupportedOperationException e) {
  LOG.warn("cleanupLevel unsupported: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling expireSnapshots.cleanupLevel(CleanupLevel.X) on an ExpireSnapshots implementation (e.g. an older catalog or third-party action) that has not overridden cleanupLevel.

Common situations: Mixing a newer Iceberg API client with an older engine/catalog implementation; custom ExpireSnapshots implementations that were not updated when CleanupLevel was introduced.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/74f19f9be50ab6e0. Report an issue: GitHub.