{"record":{"id":"28dc713b03bdec1e","repo":"apache/iceberg","slug":"cannot-satisfy-time-filters-time-range-may-includ","errorCode":null,"errorMessage":"Cannot satisfy time filters: time range may include expired snapshots","messagePattern":"Cannot satisfy time filters: time range may include expired snapshots","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/ScanSummary.java","lineNumber":191,"sourceCode":"        for (Map.Entry<Long, Long> entry : snapshotTimestamps.entrySet()) {\n          long snapshotId = entry.getKey();\n          long timestamp = entry.getValue();\n\n          if (timestamp < oldestSnapshot.timestampMillis()) {\n            oldestSnapshot = ops.current().snapshot(snapshotId);\n          }\n\n          if (timestamp >= minTimestamp && timestamp <= maxTimestamp) {\n            snapshotsInTimeRange.add(snapshotId);\n          }\n        }\n\n        // if oldest known snapshot is in the range, then there may be an expired snapshot that has\n        // been removed that matched the range. because the timestamp of that snapshot is unknown,\n        // it can't be included in the results and the results are not reliable.\n        if (snapshotsInTimeRange.contains(oldestSnapshot.snapshotId())\n            && minTimestamp < oldestSnapshot.timestampMillis()) {\n          throw new IllegalArgumentException(\n              \"Cannot satisfy time filters: time range may include expired snapshots\");\n        }\n\n        // filter down to the set of manifest files that were added after the start of the\n        // time range. manifests after the end of the time range must be included because\n        // compaction may create a manifest after the time range that includes files added in the\n        // range.\n        manifests =\n            Iterables.filter(\n                manifests,\n                manifest -> {\n                  if (manifest.snapshotId() == null) {\n                    return true; // can't tell when the manifest was written, so it may contain\n                    // matches\n                  }\n\n                  Long timestamp = snapshotTimestamps.get(manifest.snapshotId());\n                  // if the timestamp is null, then its snapshot has expired. the check for the","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/ScanSummary.java#L173-L209","documentation":"ScanSummary.build throws IllegalArgumentException when the requested time range includes the oldest known snapshot but starts before that snapshot's timestamp: snapshots may have been expired, so results for the range would be unreliable/incomplete. Iceberg refuses to return partial summary data silently.","triggerScenarios":"Calling ScanSummary with time filters (e.g. snapshot summaries by made-current time) where minTimestamp < oldestSnapshot.timestampMillis() and the oldest snapshot is itself inside the range.","commonSituations":"See trigger scenarios.","solutions":["Narrow the time range to start at or after the oldest retained snapshot's timestamp.","Disable/relax snapshot expiration or retain older snapshots if full-range summaries are needed.","Catch IllegalArgumentException and surface 'results unavailable due to expiration' to users."],"exampleFix":"// before\nScanSummary.filesAddedSinceTime(table, startTimeOfExpiredSnapshot);\n// after\nlong oldest = table.currentSnapshot() ... oldest retained snapshot timestampMillis();\nScanSummary.filesAddedSinceTime(table, Math.max(startTime, oldest));","handlingStrategy":"try-catch","validationCode":"Snapshot oldest = Iterables.getFirst(table.snapshots(), null);\nif (oldest != null && rangeStart < oldest.timestampMillis()) { /* range may include expired snapshots */ }","typeGuard":null,"tryCatchPattern":"try { summary = ScanSummary.build(...); } catch (IllegalArgumentException e) { /* narrow time range or report unreliability */ }","preventionTips":["Clamp query ranges to the oldest retained snapshot timestamp","Account for expireSnapshots retention policy in dashboards","Preserve snapshots if historical summaries are required"],"tags":["iceberg","scan-summary","snapshot-expiration","time-range"],"backgroundTag":"invalid-argument-value","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}