apache/iceberg · error · UncheckedSQLException
Failed to create lock
Error message
Failed to create %s lock
What it means
After a SQLException during lock-row insertion, tryLock checks whether the lock was actually created behind the scenes by comparing instance IDs; if the lock row belongs to a different instance (i.e. insertion genuinely failed and was not a duplicate), it throws UncheckedSQLException with "Failed to create %s lock" (%s formatted with the lock object). It means another owner may hold the lock or the insert failed for a real SQL reason.
Solutions
- Check who currently holds the lock (query the lock table's instance/creation columns) and wait for it to be released.
- Remove stale lock rows left behind by crashed jobs after confirming no active owner.
- Inspect the wrapped SQLException cause for permission/constraint errors on the lock table.
- Ensure each job uses a distinct lock ID to avoid cross-job contention.
Example fix
// before (stale row blocks creation) DELETE nothing; tryLock fails // after (after confirming owner is dead) DELETE FROM iceberg_lock WHERE name = 'maintenance' AND instance_id = 'dead-job-instance';
Defensive patterns
Strategy: try-catch
Validate before calling
// check lock ownership before attempting SELECT instance_id, created_at FROM iceberg_lock WHERE name = ?;
Try / catch
try {
if (!lock.tryLock()) { /* handle contention */ }
} catch (UncheckedSQLException e) {
// inspect cause; check the lock table for a stale/foreign owner
// clean stale rows only after confirming the owner is dead
} Prevention
- Use unique lock IDs per job to avoid contention
- Add lock-row expiry/TTL handling for crashed owners
- Monitor the lock table for stale rows
- Log instance IDs to identify competing owners
When it happens
Trigger: JdbcLock.tryLock when the INSERT raises SQLException AND instanceId() returns an ID different from newInstanceId — i.e. the lock row exists but was created by another process, or the insert failed for a non-duplicate-key reason.
Common situations: Two Flink jobs/maintenance triggers contending for the same lock ID, stale lock rows left by a crashed job, or DB constraint/permission errors on insert.
Related errors
- Cannot initialize JDBC table maintenance lock
- Cannot initialize JDBC table maintenance lock
- Failed to check the state of the lock
- Failed to create lock
- Failed to create lock
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/42365c27fa49e579.
Report an issue: GitHub.
Appendix: source
Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/JdbcLockFactory.java:206
int count = sql.executeUpdate();
LOG.info(
"Created {} lock with instanceId {} with row count {}",
this,
newInstanceId,
count);
return count == 1;
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UncheckedInterruptedException(e, "Interrupted during tryLock");
} catch (SQLException e) {
// SQL exception happened when creating the lock. Check if the lock creation was
// successful behind the scenes.
if (newInstanceId.equals(instanceId())) {
return true;
} else {
throw new UncheckedSQLException(e, "Failed to create %s lock", this);
}
}
}
@SuppressWarnings("checkstyle:NestedTryDepth")
@Override
public boolean isHeld() {
try {
return pool.run(
conn -> {
try (PreparedStatement sql = conn.prepareStatement(GET_LOCK_SQL)) {
sql.setString(1, type.key);
sql.setString(2, lockId);
try (ResultSet rs = sql.executeQuery()) {
return rs.next();
}
}
});View on GitHub (pinned to 86d9c8fc54)