apache/iceberg · error · UncheckedIOException
Cannot update properties on namespace '%s': %s
Error message
Cannot update properties on namespace '%s': %s
What it means
updateProperties commits a namespace-objects Put via commitRetry; when the commit fails with NessieReferenceConflictException that is NOT the benign KEY_DOES_NOT_EXIST conflict, the client wraps it in an UncheckedIOException 'Cannot update properties on namespace ... : <server message>'. This signals a merge-style conflict on the ref (content changed concurrently) that Iceberg surfaces as a generic IO failure.
Source
Thrown at nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java:434
action.accept(newProperties);
org.projectnessie.model.Namespace updatedNamespace =
org.projectnessie.model.Namespace.builder()
.from(oldNamespace)
.properties(newProperties)
.build();
commitBuilder.operation(Operation.Put.of(key, updatedNamespace));
return commitBuilder;
});
// always successful, otherwise an exception is thrown
return true;
} catch (NessieReferenceConflictException e) {
Optional<Conflict> conflict =
NessieUtil.extractSingleConflict(e, EnumSet.of(Conflict.ConflictType.KEY_DOES_NOT_EXIST));
if (conflict.isPresent()
&& conflict.get().conflictType() == Conflict.ConflictType.KEY_DOES_NOT_EXIST) {
throw new NoSuchNamespaceException(e, "Namespace does not exist: %s", namespace);
}
throw new UncheckedIOException(
String.format(
"Cannot update properties on namespace '%s': %s", namespace, e.getMessage()),
e);
} catch (NessieContentNotFoundException e) {
throw new NoSuchNamespaceException("Namespace does not exist: %s", namespace);
} catch (NessieReferenceNotFoundException e) {
throw new UncheckedIOException(
String.format(
"Cannot update properties on namespace '%s': ref '%s' is no longer valid.",
namespace, getRef().getName()),
e);
} catch (BaseNessieClientServerException e) {
throw new UncheckedIOException(
String.format("Cannot update namespace '%s': %s", namespace, e.getMessage()), e);
}
}
public void renameTable(TableIdentifier from, TableIdentifier to) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Retry the setProperties/removeProperties call — updateProperties already retries via commitRetry, but persistent conflicts require re-running after the other writer finishes.
- Serialize namespace property updates through a single writer or use a mutable branch dedicated to metadata changes.
- Inspect the embedded server conflict message (e.getMessage()) to identify the conflicting key and coordinate with the other writer.
- If conflicts are frequent, switch the workload to write to per-job branches and merge deliberately.
Example fix
// before
catalog.setProperties(ns, props); // UncheckedIOException on concurrent conflict
// after
try {
catalog.setProperties(ns, props);
} catch (UncheckedIOException e) {
// transient concurrent-modification conflict; back off and retry
Thread.sleep(1000);
catalog.setProperties(ns, props);
} Defensive patterns
Strategy: retry
Validate before calling
boolean busy = recentWritersOn(namespace); // your own coordination/lock mechanism
if (busy) { waitUntilFree(namespace); } Try / catch
int attempts = 3;
while (attempts-- > 0) {
try { catalog.setProperties(ns, props); break; }
catch (UncheckedIOException e) {
if (attempts == 0 || !isConflictMessage(e)) throw e;
sleep(backoff);
}
} Prevention
- Single-writer discipline for namespace property updates.
- Add retry with exponential backoff around property mutations.
- Avoid rebasing/merging into a branch while metadata writers are active.
- Use dedicated metadata branches if multiple tools mutate namespaces.
When it happens
Trigger: setProperties/removeProperties where another commit concurrently modified the same namespace key (or another conflicting key) on the branch, producing a reference conflict other than KEY_DOES_NOT_EXIST; the conflict cannot be attributed to a missing namespace.
Common situations: Two writers updating the same namespace properties simultaneously; a rebase/merge on the Nessie branch introducing conflicts; namespace object changed by a different tool (e.g. Spark vs Flink jobs) between read and commit.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot create namespace '%s': %s
- Cannot drop namespace '%s': %s
- Namespace already exists: %s
- Namespace already exists: %s
- namespace %s(%s) has already existed
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/9440f6e0c079407d.
Report an issue: GitHub.