apache/iceberg · error · NamespaceNotEmptyException

%s is not empty: %s

Error message

%s is not empty: %s

What it means

NamespaceNotEmptyException from BigQueryMetastoreClientImpl.delete wrapping the upstream BigQuery error when a dataset cannot be deleted because it still contains tables. The client rethrows with the dataset id plus the original message so the caller knows why the drop failed.

Source

Thrown at bigquery/src/main/java/org/apache/iceberg/gcp/bigquery/BigQueryMetastoreClientImpl.java:219

  @Override
  public void delete(DatasetReference datasetReference) {
    try {
      HttpResponse response =
          client
              .datasets()
              .delete(datasetReference.getProjectId(), datasetReference.getDatasetId())
              .executeUnparsed();
      if (response.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
        throw new NoSuchNamespaceException(
            "Namespace does not exist: %s", datasetReference.getDatasetId());
      }

      convertExceptionIfUnsuccessful(response);
    } catch (IOException e) {
      throw new RuntimeIOException(e);
    } catch (NamespaceNotEmptyException e) {
      throw new NamespaceNotEmptyException(
          "%s is not empty: %s", datasetReference.getDatasetId(), e.getMessage());
    }
  }

  @Override
  public boolean setParameters(DatasetReference datasetReference, Map<String, String> parameters) {
    Dataset dataset = load(datasetReference);
    ExternalCatalogDatasetOptions existingOptions = dataset.getExternalCatalogDatasetOptions();

    Map<String, String> existingParameters =
        (existingOptions == null || existingOptions.getParameters() == null)
            ? Maps.newHashMap() // Use HashMap to allow modification below
            : Maps.newHashMap(existingOptions.getParameters()); // Copy to compare later

    // Calculate what the new parameters would be.
    Map<String, String> newParameters = Maps.newHashMap(existingParameters);
    newParameters.putAll(parameters);

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Drop all tables first (catalog.listTables then dropTable each) before dropping the namespace
  2. Use catalog.dropNamespace(ns, true) purge semantics if you intend recursive deletion where supported
  3. Inspect the dataset in BigQuery console for foreign tables blocking deletion
  4. Catch NamespaceNotEmptyException in teardown scripts and handle cleanup explicitly

Example fix

// before
catalog.dropNamespace(ns); // throws if dataset has tables
// after
for (TableIdentifier t : catalog.listTables(ns)) {
  catalog.dropTable(t);
}
catalog.dropNamespace(ns);
Defensive patterns

Strategy: validation

Validate before calling

boolean empty = catalog.listTables(ns).isEmpty(); if (!empty) { /* drop tables first */ }

Try / catch

try { catalog.dropNamespace(ns); } catch (NamespaceNotEmptyException e) { /* clean up tables then retry */ }

Prevention

When it happens

Trigger: Calling dropNamespace / client.delete on a dataset that still holds tables (Iceberg or non-Iceberg), where BigQuery refuses datasets.delete and the response surfaces a 'not empty' condition.

Common situations: dropNamespace without purge on a namespace that still has registered tables, leftover non-Iceberg tables inside the dataset, failed earlier table drops leaving orphans.

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/488c68ca077c0492. Report an issue: GitHub.