apache/iceberg · error · NamespaceNotEmptyException

Namespace '%s' is not empty.

Error message

Namespace '%s' is not empty.

What it means

When the drop commit conflicts server-side with NAMESPACE_NOT_EMPTY, the client rethrows as NamespaceNotEmptyException. Nessie enforces that a namespace key cannot be deleted while nested namespaces or tables still live under it.

Source

Thrown at nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java:349

            "Content object with name '%s' is not a namespace.", namespace);
      }
      try {
        commitRetry("drop namespace " + key, Operation.Delete.of(key));
        return true;
      } catch (NessieReferenceConflictException e) {
        Optional<Conflict> conflict =
            NessieUtil.extractSingleConflict(
                e,
                EnumSet.of(
                    Conflict.ConflictType.KEY_DOES_NOT_EXIST,
                    Conflict.ConflictType.NAMESPACE_NOT_EMPTY));
        if (conflict.isPresent()) {
          Conflict.ConflictType conflictType = conflict.get().conflictType();
          switch (conflictType) {
            case KEY_DOES_NOT_EXIST:
              return false;
            case NAMESPACE_NOT_EMPTY:
              throw new NamespaceNotEmptyException(e, "Namespace '%s' is not empty.", namespace);
          }
        }
        throw new UncheckedIOException(
            String.format("Cannot drop namespace '%s': %s", namespace, e.getMessage()), e);
      }
    } catch (NessieNotFoundException e) {
      LOG.error(
          "Cannot drop namespace '{}': ref '{}' is no longer valid.",
          namespace,
          getRef().getName(),
          e);
    } catch (BaseNessieClientServerException e) {
      throw new UncheckedIOException(
          String.format("Cannot drop namespace '%s': %s", namespace, e.getMessage()), e);
    }
    return false;
  }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Drop or move all tables and views under the namespace first (listTables + dropTable).
  2. Drop child namespaces (deepest first) before the parent.
  3. Re-check after concurrent writers: re-list contents and retry the drop.

Example fix

// before
catalog.dropNamespace(Namespace.of("a")); // still contains tables
// after
for (TableIdentifier t : catalog.listTables(Namespace.of("a"))) {
  catalog.dropTable(t);
}
catalog.dropNamespace(Namespace.of("a"));
Defensive patterns

Strategy: validation

Validate before calling

boolean empty = catalog.listTables(ns).isEmpty()
    && catalog.listNamespaces(ns).isEmpty();
if (!empty) throw new IllegalStateException("Namespace not empty: " + ns);

Try / catch

try {
  return catalog.dropNamespace(ns);
} catch (NamespaceNotEmptyException e) {
  // drain children deepest-first, then retry
  return drainAndDrop(ns);
}

Prevention

When it happens

Trigger: Calling Catalog.dropNamespace(Namespace.of("a")) while tables/views or child namespaces like 'a.b' or 'a.tbl' still exist under it on the current ref.

Common situations: Trying to clean up a parent namespace before migrating its tables; concurrent writers added a table between your check and the drop.

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


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