apache/iceberg · error · UncheckedIOException

Failed to close table loader

Error message

Failed to close table loader

What it means

IcebergSource.planSplitsForBatch plans batch splits inside a try-with-resources on the TableLoader; an IOException during closing the table loader is wrapped in UncheckedIOException with the (arguably misleading) message 'Failed to close table loader'. The failure occurs during batch split discovery when the loader's IO cannot be cleanly closed after planning.

Source

Thrown at flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/source/IcebergSource.java:166

   */
  private List<IcebergSourceSplit> planSplitsForBatch(String threadName) {
    if (batchSplits != null) {
      return batchSplits;
    }

    ExecutorService workerPool =
        ThreadPools.newFixedThreadPool(threadName, scanContext.planParallelism());
    try (TableLoader loader = tableLoader.clone()) {
      loader.open();
      this.batchSplits =
          FlinkSplitPlanner.planIcebergSourceSplits(loader.loadTable(), scanContext, workerPool);
      LOG.info(
          "Discovered {} splits from table {} during job initialization",
          batchSplits.size(),
          tableName);
      return batchSplits;
    } catch (IOException e) {
      throw new UncheckedIOException("Failed to close table loader", e);
    } finally {
      workerPool.shutdown();
    }
  }

  @Override
  public Boundedness getBoundedness() {
    return scanContext.isStreaming() ? Boundedness.CONTINUOUS_UNBOUNDED : Boundedness.BOUNDED;
  }

  @Override
  public SourceReader<T, IcebergSourceSplit> createReader(SourceReaderContext readerContext) {
    IcebergSourceReaderMetrics metrics =
        new IcebergSourceReaderMetrics(readerContext.metricGroup(), tableName);
    return new IcebergSourceReader<>(
        emitter, metrics, readerFunction, splitComparator, readerContext);
  }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check the chained IOException cause to see whether the real failure is close() or the planning step
  2. Upgrade Iceberg — loader close failures are often fixed in newer versions' FileIO implementations
  3. If using a custom TableLoader, make close() idempotent and null-safe
  4. Retry job startup; investigate storage connectivity if close failures recur

Example fix

// before
FlinkSource.forRowData().tableLoader(customLoader).build(); // customLoader.close() throws
// after: make close idempotent
public void close() {
  if (!closed) {
    closed = true;
    io.close();
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the custom loader closes cleanly
TableLoader loader = TableLoader.fromCatalog(...);
loader.open();
loader.loadTable();
loader.close();

Try / catch

try {
  source = IcebergSource.forRowData().tableLoader(loader).build();
} catch (UncheckedIOException e) {
  LOG.error("Table loader lifecycle failure", e.getCause());
  throw e;
}

Prevention

When it happens

Trigger: Batch mode IcebergSource split planning where TableLoader.close() throws IOException (e.g. underlying FileSystem/FileIO close fails, cached connections broken), or an earlier IOException from planning propagated through the resource-close path.

Common situations: Hadoop FileSystem cache/close issues, storage clients with leaked connections after long-running jobs, custom TableLoader implementations throwing on close.

Related errors


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