apache/iceberg · error · UncheckedIOException
Failed to close manifest reader
Error message
Failed to close manifest reader
What it means
When SnapshotChanges caches data-file changes it reads all relevant data manifests and closes the combined CloseableIterable in a try-with-resources block. If closing (or iterating) the manifest reader throws an IOException, it is rethrown as an UncheckedIOException with message 'Failed to close manifest reader'. It signals an I/O problem reading manifest files from the underlying FileIO, not a data corruption of the snapshot itself.
Source
Thrown at core/src/main/java/org/apache/iceberg/SnapshotChanges.java:151
manifest -> Objects.equals(manifest.snapshotId(), snapshot.snapshotId()));
Iterable<CloseableIterable<Pair<ManifestEntry.Status, DataFile>>> manifestReadTasks =
Iterables.transform(relevantDataManifests, this::readDataManifest);
try (CloseableIterable<Pair<ManifestEntry.Status, DataFile>> changedDataFiles =
iterate(manifestReadTasks)) {
for (Pair<ManifestEntry.Status, DataFile> pair : changedDataFiles) {
switch (pair.first()) {
case ADDED:
adds.add(pair.second());
break;
case DELETED:
deletes.add(pair.second());
break;
}
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to close manifest reader", e);
}
this.addedDataFiles = adds.build();
this.removedDataFiles = deletes.build();
}
private CloseableIterable<Pair<ManifestEntry.Status, DataFile>> readDataManifest(
ManifestFile manifest) {
CloseableIterable<ManifestEntry<DataFile>> entries =
ManifestFiles.read(manifest, io, specsById).entries();
CloseableIterable<ManifestEntry<DataFile>> relevant =
CloseableIterable.filter(entries, e -> e.status() != ManifestEntry.Status.EXISTING);
return CloseableIterable.transform(
relevant,
entry -> {
if (entry.status() == ManifestEntry.Status.ADDED) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Inspect the wrapped IOException cause to identify the real I/O failure (missing file, permissions, network).
- Retry the read — manifest reads are non-mutating and safe to repeat after a transient failure.
- Verify the manifest files still exist and are readable via the table's FileIO (check concurrent expiration/deletion).
- Refresh table metadata and re-create the Snapshot/SnapshotChanges if the snapshot is stale or its files were removed.
Defensive patterns
Strategy: try-catch
Validate before calling
// verify manifests exist before reading changes
for (ManifestFile m : snapshot.dataManifests(io)) {
if (Objects.equals(m.snapshotId(), snapshot.snapshotId())) {
Preconditions.checkArgument(io.newInputFile(m.path()).exists(),
"Missing manifest: %s", m.path());
}
} Try / catch
try {
Iterable<DataFile> added = changes.addedDataFiles();
} catch (UncheckedIOException e) {
if (e.getMessage().contains("Failed to close manifest reader")) {
LOG.warn("Transient manifest I/O failure, retrying after refresh", e);
table.refresh();
changes = SnapshotChanges.Builder.buildFrom(table.currentSnapshot(), ...);
added = changes.addedDataFiles();
} else {
throw e;
}
} Prevention
- Ensure snapshot expiration jobs don't delete manifests still being read by concurrent consumers.
- Use retry/backoff around snapshot-diff reads when using flaky object storage.
- Keep credentials/session tokens valid for the duration of metadata reads.
- Refresh table metadata before reading changes from long-lived snapshot references.
When it happens
Trigger: Calling SnapshotChanges.addedDataFiles() or removedDataFiles() (first access triggers cacheDataFileChanges) while the underlying file system fails — e.g. missing/unreadable manifest file, network/credential failure to object storage, or an IOException thrown during close of the manifest readers.
Common situations: Manifest files deleted or expired concurrently (e.g. expireSnapshots removing files still referenced by an in-flight read); transient S3/HDFS access failures; expired cloud credentials mid-read; container/file-system interruptions.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Failed to create file: %s
- Failed to delete: %s
- File already exists: %s
- Failed to create the file's directory at %s.
- this.getClass().getName() + " doesn't implement removedDelet
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/819997d47953dd6d.
Report an issue: GitHub.