apache/iceberg · error · UncheckedIOException
Failed to create tableMaintenance
Error message
Failed to create tableMaintenance
What it means
IcebergSink.addPostCommitTopology builds the TableMaintenance operator from the maintenance config; if constructing the lock config or the maintenance builder throws an IOException it is wrapped in UncheckedIOException 'Failed to create tableMaintenance'. The cause reveals whether lock-factory setup or IO during operator creation failed.
Source
Thrown at flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/sink/IcebergSink.java:305
} else {
builder = TableMaintenance.forChangeStream(tableChangeStream, tableLoader);
}
builder
.uidSuffix(tableMaintenanceUid)
.add(maintenanceTasks)
.rateLimit(Duration.ofSeconds(flinkMaintenanceConfig.rateLimit()))
.lockCheckDelay(Duration.ofSeconds(flinkMaintenanceConfig.lockCheckDelay()))
.parallelism(flinkMaintenanceConfig.parallelism());
String slotSharingGroup = flinkMaintenanceConfig.slotSharingGroup();
if (slotSharingGroup != null) {
builder.slotSharingGroup(slotSharingGroup);
}
builder.append();
} catch (IOException e) {
throw new UncheckedIOException("Failed to create tableMaintenance ", e);
}
}
@Override
public DataStream<RowData> addPreWriteTopology(DataStream<RowData> inputDataStream) {
return distributeDataStream(inputDataStream);
}
@Override
public DataStream<CommittableMessage<IcebergCommittable>> addPreCommitTopology(
DataStream<CommittableMessage<WriteResult>> writeResults) {
TypeInformation<CommittableMessage<IcebergCommittable>> typeInformation =
CommittableMessageTypeInfo.of(this::getCommittableSerializer);
String suffix = defaultSuffix(uidSuffix, table.name());
String preCommitAggregatorUid = String.format("Sink pre-commit aggregator: %s", suffix);
// global forces all output records send to subtask 0 of the downstream committer operator.View on GitHub (pinned to 86d9c8fc54)
Solutions
- Read the chained IOException cause for the real failure (lock store connection, missing resource, etc.)
- Verify maintenance lock config properties (lock type, lock store URI/credentials) — try running without lock config (no lockType) to isolate
- Confirm the lock backend (e.g. JDBC database, ZooKeeper quorum) is reachable from the job manager
- Validate each configured maintenance task (expire snapshots, orphan cleanup, rewrite data files) can run against the table with the job's FileIO credentials
Example fix
// before
TableMaintenance.forChangeStream(changeStream, loader)
.add(RewriteDataFilesExecutor.fromConfig(table)) // throws IOException
.append();
// after
// ensure lock/task IO deps are configured & reachable first
LockConfig lockConfig = flinkMaintenanceConfig.createLockConfig();
LOG.info("maintenance lock type={}", lockConfig.lockType());
// fix lock store endpoint/credentials, then retry append() Defensive patterns
Strategy: validation
Validate before calling
// before enabling maintenance, verify lock config resolves and backend is reachable
LockConfig lockConfig = flinkMaintenanceConfig.createLockConfig();
if (StringUtils.isNotEmpty(lockConfig.lockType())) {
LockFactory f = LockFactoryBuilder.build(lockConfig, tableName); // throws early if misconfigured
LOG.info("maintenance lock: {} via {}", lockConfig.lockType(), f);
} Try / catch
try {
sink.addPostCommitTopology(committables);
} catch (UncheckedIOException e) {
LOG.error("tableMaintenance creation failed: {}", e.getCause(), e);
// disable maintenance tasks and resubmit to isolate the cause
throw e;
} Prevention
- Validate table.maintenance.* lock properties (type, URI, credentials) before submission
- Test the lock backend connectivity from the job manager host
- Enable one maintenance task at a time to isolate IO failures
- Keep the no-lock fallback path (empty lockType) as an isolation lever
When it happens
Trigger: Enabling table maintenance (maintenance config) where TableMaintenance.Builder.append() or LockFactoryBuilder.build/createLockConfig performs IO that fails — e.g. creating/reaching a distributed lock (JDBC, ZooKeeper) or reading lock-related configuration resources.
Common situations: Misconfigured table.maintenance.* lock properties (bad lock type/URL), lock store database/table unreachable, missing credentials for the lock backend, or a maintenance task builder doing IO that fails (catalog access during task setup).
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Failed to create tableMaintenance
- Failed to list partitions of table %s
- Failed to load iceberg table from table loader: ${tableLoade
- Failed to load iceberg table from table loader: ${tableLoade
- Unexpected delete file content:
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/6db39bdc38a373a5.
Report an issue: GitHub.