apache/iceberg · critical · IllegalStateException

Cannot load current offset at snapshot %d, the snapshot was

Error message

Cannot load current offset at snapshot %d, the snapshot was expired or removed

What it means

Spark streaming reads track progress via StreamingOffsets that reference a snapshot ID. When resuming or planning the next micro-batch, the stored snapshot must still exist; if table snapshot expiration removed it, validateCurrentSnapshotExists throws IllegalStateException so the stream fails instead of silently skipping data.

Source

Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SyncSparkMicroBatchPlanner.java:242

        startPosOfSnapOffset = -1;
        // if anyhow we are moving to next snapshot we should only scan addedFiles
        scanAllFiles = false;
      }
    }

    StreamingOffset latestStreamingOffset =
        new StreamingOffset(curSnapshot.snapshotId(), curPos, scanAllFiles);

    // if no new data arrived, then return null.
    return latestStreamingOffset.equals(startingOffset) ? null : latestStreamingOffset;
  }

  @Override
  public void stop() {}

  private void validateCurrentSnapshotExists(Snapshot snapshot, StreamingOffset currentOffset) {
    if (snapshot == null) {
      throw new IllegalStateException(
          String.format(
              Locale.ROOT,
              "Cannot load current offset at snapshot %d, the snapshot was expired or removed",
              currentOffset.snapshotId()));
    }
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Restore the stream from a checkpoint taken before the referenced snapshot was expired, or start a new stream with a fresh checkpoint (accepting a new starting snapshot)
  2. Pause snapshot expiration (increase retention / stop expireSnapshots) while streams are active; keep min-snapshot-retention above max stream pause time
  3. If data loss from restarting is unacceptable, recover the expired snapshot from backups/metadata and reload the table state
  4. Set expireSnapshots' older-than based on the oldest streaming checkpoint's snapshot id before expiring

Example fix

// before
spark.sql("CALL catalog.system.expire_snapshots('db.t', TIMESTAMP '2026-08-01 00:00:00')");
// after
// keep at least 7 days so active streams' offsets remain valid
spark.sql("CALL catalog.system.expire_snapshots('db.t', TIMESTAMP '2026-09-04 00:00:00', map('streaming-max-snapshot-age-ms','604800000'))");
Defensive patterns

Strategy: try-catch

Validate before calling

// before resuming a stream, verify the checkpoint's snapshot still exists
Snapshot s = table.snapshot(currentOffset.snapshotId());
if (s == null) {
  throw new IllegalStateException("Checkpoint snapshot " + currentOffset.snapshotId()
      + " expired; restart stream from a valid snapshot");
}

Try / catch

try {
  streamQuery.processAllAvailable();
} catch (IllegalStateException e) {
  if (e.getMessage().contains("the snapshot was expired or removed")) {
    // restart stream with fresh checkpoint or restore the snapshot
  } else { throw e; }
}

Prevention

When it happens

Trigger: A running Spark structured streaming query resumes from a checkpoint whose offset references snapshot N, but snapshot N was expired by expireSnapshots, retention policies, or a concurrent table maintenance job.

Common situations: expireSnapshots run (manually or via a scheduled cleanup) with a retention shorter than the stream's checkpoint age; shared table where another team expires snapshots; long-paused stream resumed after snapshot cleanup.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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