apache/iceberg · error · UncheckedIOException

Failed to close manifest reader

Error message

Failed to close manifest reader

What it means

While caching delete-file changes, BaseSnapshot iterates manifests with a ManifestReader in try-with-resources; closing the reader throws an IOException. Iceberg wraps it in UncheckedIOException with message "Failed to close manifest reader" because the failure happens at cleanup time inside a method that cannot throw checked exceptions.

Source

Thrown at core/src/main/java/org/apache/iceberg/BaseSnapshot.java:290

            deleteManifests(fileIO), manifest -> Objects.equal(manifest.snapshotId(), snapshotId));

    for (ManifestFile manifest : changedManifests) {
      try (ManifestReader<DeleteFile> reader =
          ManifestFiles.readDeleteManifest(manifest, fileIO, null)) {
        for (ManifestEntry<DeleteFile> entry : reader.entries()) {
          switch (entry.status()) {
            case ADDED:
              adds.add(entry.file().copy());
              break;
            case DELETED:
              deletes.add(entry.file().copyWithoutStats());
              break;
            default:
              // ignore existing
          }
        }
      } catch (IOException e) {
        throw new UncheckedIOException("Failed to close manifest reader", e);
      }
    }

    this.addedDeleteFiles = adds.build();
    this.removedDeleteFiles = deletes.build();
  }

  private void cacheDataFileChanges(FileIO fileIO) {
    Preconditions.checkArgument(fileIO != null, "Cannot cache data file changes: FileIO is null");

    ImmutableList.Builder<DataFile> adds = ImmutableList.builder();
    ImmutableList.Builder<DataFile> deletes = ImmutableList.builder();

    // read only manifests that were created by this snapshot
    Iterable<ManifestFile> changedManifests =
        Iterables.filter(
            dataManifests(fileIO), manifest -> Objects.equal(manifest.snapshotId(), snapshotId));
    try (CloseableIterable<ManifestEntry<DataFile>> entries =

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Retry after table.refresh(); the failure is usually transient IO
  2. Verify the manifest files referenced by the snapshot exist and are readable via the FileIO
  3. Restore metadata from backup if the manifest is truncated/corrupt (e.g. rewrite with the metadata JSON)
  4. Exclude competing expire/cleanup jobs while incrementally scanning changes

Example fix

// before
List<DeleteFile> adds = snapshot.addedDeleteFiles();
// after
try {
  List<DeleteFile> adds = snapshot.addedDeleteFiles();
} catch (UncheckedIOException e) {
  table.refresh();
  List<DeleteFile> adds = snapshot.addedDeleteFiles(); // retry transient IO
}
Defensive patterns

Strategy: try-catch

Validate before calling

ManifestFile m = snapshot.deleteManifests(io).get(0);
if (!io.newInputFile(m.path()).exists()) throw new IllegalStateException("delete manifest missing");

Try / catch

try { snapshot.addedDeleteFiles(); } catch (UncheckedIOException e) { table.refresh(); /* retry or repair metadata */ }

Prevention

When it happens

Trigger: Calling addedDeleteFiles()/removedDeleteFiles() on a v2 snapshot whose delete manifests cannot be read or closed — network failure to object storage mid-iteration, corrupted/truncated manifest file, or IO backend error on close.

Common situations: S3/GCS transient errors while reading incremental changes; manifests deleted by concurrent cleanup; truncated metadata files after an interrupted write.

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


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