apache/iceberg · error · UnsupportedOperationException

Cannot clean files incrementally when snapshot IDs are speci

Error message

Cannot clean files incrementally when snapshot IDs are specified

What it means

RemoveSnapshots.validateCleanupCanBeIncremental rejects incremental file cleanup when the expiration was targeted at a specific snapshot ID (expireSnapshotsTo/expireSnapshotsById). Incremental cleanup is only valid for the default timestamp-based expiration of main-ancestry snapshots, so the combination is rejected with UnsupportedOperationException.

Source

Thrown at core/src/main/java/org/apache/iceberg/RemoveSnapshots.java:415

              && !hasNonMainSnapshots(current);
    }

    LOG.info(
        "Cleaning up expired files (local, {})", incrementalCleanup ? "incremental" : "reachable");

    FileCleanupStrategy cleanupStrategy =
        incrementalCleanup
            ? new IncrementalFileCleanup(
                ops.io(), deleteExecutorService, planExecutorService(), deleteFunc)
            : new ReachableFileCleanup(
                ops.io(), deleteExecutorService, planExecutorService(), deleteFunc);

    cleanupStrategy.cleanFiles(base, current, cleanupLevel);
  }

  private void validateCleanupCanBeIncremental(TableMetadata current) {
    if (specifiedSnapshotId) {
      throw new UnsupportedOperationException(
          "Cannot clean files incrementally when snapshot IDs are specified");
    }

    if (hasRemovedNonMainAncestors(base, current)) {
      throw new UnsupportedOperationException(
          "Cannot incrementally clean files when snapshots outside of main ancestry were removed");
    }

    if (hasNonMainSnapshots(current)) {
      throw new UnsupportedOperationException(
          "Cannot incrementally clean files when there are snapshots outside of main");
    }
  }

  private boolean hasRemovedNonMainAncestors(
      TableMetadata beforeExpiration, TableMetadata afterExpiration) {
    Set<Long> mainAncestors = mainAncestors(beforeExpiration);
    for (Snapshot snapshotBeforeExpiration : beforeExpiration.snapshots()) {

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Drop the snapshot-ID targeting and use default (timestamp/ancestry-based) expiration if incremental cleanup is desired
  2. Keep the specified snapshot ID but switch the cleanup level to full (e.g. do not request incremental cleanup; use the full cleanup strategy)
  3. Split the work: run targeted expiration without incremental cleanup, then a normal expiration with incremental cleanup

Example fix

// before
expireSnapshots(table)
    .cleanExpiredFiles(true)
    .withIncrementalCleanup()
    .expireSnapshotsId(12345L)
    .execute();
// after
expireSnapshots(table)
    .cleanExpiredFiles(true)
    .withIncrementalCleanup()
    .execute(); // default ancestry-based expiration, or remove withIncrementalCleanup() if ID targeting is required
Defensive patterns

Strategy: validation

Validate before calling

// before enabling incremental cleanup, ensure no snapshot ID was specified
boolean specifiedSnapshotId = /* tracked from expireSnapshotsId/To calls */;
if (specifiedSnapshotId) {
  throw new IllegalArgumentException("Incremental cleanup requires default (non-ID-targeted) expiration");
}

Try / catch

try {
  expireSnapshots(table).withIncrementalCleanup().execute();
} catch (UnsupportedOperationException e) {
  // rerun without incremental cleanup or without ID targeting
}

Prevention

When it happens

Trigger: Calling expireSnapshots(table).expireSnapshotsId(someId)... (or .expireSnapshotsTo(timestamp) with a specified ID path) while cleanup level is incremental — i.e., cleanExpiredFiles enabled with incremental strategy and specifiedSnapshotId is true.

Common situations: Users combining programmatic per-snapshot expiration APIs with the incremental cleanup option introduced for timestamp-based expiration; configuration defaults enabling incremental cleanup while scripts call expireById.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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