apache/iceberg · error · IllegalArgumentException

Cannot find a snapshot after: {scanContext.startSnapshotTime

Error message

Cannot find a snapshot after: {scanContext.startSnapshotTimestamp()}

What it means

ContinuousSplitPlannerImpl.startSnapshot uses SnapshotUtil.oldestAncestorAfter to find the first snapshot at or after startSnapshotTimestamp for the TABLE_SCAN_THEN_INCREMENT/INCREMENTAL_FROM_TIMESTAMP strategy; Preconditions.checkArgument fails when no snapshot matches, meaning no snapshot exists after the given timestamp.

Source

Thrown at flink/v2.1/flink/src/main/java/org/apache/iceberg/flink/source/enumerator/ContinuousSplitPlannerImpl.java:244

      case INCREMENTAL_FROM_LATEST_SNAPSHOT_EXCLUSIVE:
        return Optional.ofNullable(table.currentSnapshot());
      case INCREMENTAL_FROM_EARLIEST_SNAPSHOT:
        return Optional.ofNullable(SnapshotUtil.oldestAncestor(table));
      case INCREMENTAL_FROM_SNAPSHOT_ID:
        Snapshot matchedSnapshotById = table.snapshot(scanContext.startSnapshotId());
        Preconditions.checkArgument(
            matchedSnapshotById != null,
            "Start snapshot id not found in history: " + scanContext.startSnapshotId());
        return Optional.of(matchedSnapshotById);
      case INCREMENTAL_FROM_SNAPSHOT_TIMESTAMP:
        Snapshot matchedSnapshotByTimestamp =
            SnapshotUtil.oldestAncestorAfter(table, scanContext.startSnapshotTimestamp());
        Preconditions.checkArgument(
            matchedSnapshotByTimestamp != null,
            "Cannot find a snapshot after: " + scanContext.startSnapshotTimestamp());
        return Optional.of(matchedSnapshotByTimestamp);
      default:
        throw new IllegalArgumentException(
            "Unknown starting strategy: " + scanContext.streamingStartingStrategy());
    }
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check table.currentSnapshot().timestampMillis() and pick a start timestamp earlier than the oldest retained snapshot.
  2. Remove the start-snapshot-timestamp option to start from the current snapshot instead.
  3. Verify the timestamp is in milliseconds since epoch and not skewed by timezone or unit conversion.
  4. Reduce snapshot expiration aggressiveness so retained history covers the requested timestamp.

Example fix

// before
options.put("start-snapshot-timestamp", String.valueOf(System.currentTimeMillis() + drift));
// after
Snapshot s = table.currentSnapshot();
if (s != null && s.timestampMillis() > ts) {
  ts = s.timestampMillis(); // clamp to a known snapshot time
}
options.put("start-snapshot-timestamp", String.valueOf(ts));
Defensive patterns

Strategy: validation

Validate before calling

long ts = ...; // desired start timestamp in millis
Table table = tableLoader.loadTable();
long oldest = SnapshotUtil.oldestAncestor(table) != null
    ? SnapshotUtil.oldestAncestor(table).timestampMillis() : Long.MAX_VALUE;
if (ts < oldest || ts > Optional.ofNullable(table.currentSnapshot())
        .map(Snapshot::timestampMillis).orElse(Long.MAX_VALUE)) {
  ts = oldest; // clamp to a retained snapshot
}

Prevention

When it happens

Trigger: Starting a streaming/incremental scan with start-snapshot-timestamp set to a time after the table's latest snapshot (or before which all snapshots were expired), so no ancestor snapshot matches.

Common situations: Timestamp computed from another table or an older run; table is brand new or all snapshots have been expired by retention rules; timezone/unit mistakes (seconds vs milliseconds) pushing the timestamp past the last snapshot.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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