apache/iceberg · error · java.lang.IllegalArgumentException

Cannot use catalog %s(%s): not a TableCatalog

Error message

Cannot use catalog %s(%s): not a TableCatalog

What it means

asTableCatalog casts a Spark CatalogPlugin to TableCatalog and throws IllegalArgumentException when the catalog implementation does not support table operations (e.g. it is a function catalog or a generic catalog).

Source

Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:827

            catalogName -> {
              try {
                return catalogManager.catalog(catalogName);
              } catch (Exception e) {
                return null;
              }
            },
            Identifier::of,
            defaultCatalog,
            currentNamespace);
    return new CatalogAndIdentifier(catalogIdentifier);
  }

  private static TableCatalog asTableCatalog(CatalogPlugin catalog) {
    if (catalog instanceof TableCatalog) {
      return (TableCatalog) catalog;
    }

    throw new IllegalArgumentException(
        String.format(
            "Cannot use catalog %s(%s): not a TableCatalog",
            catalog.name(), catalog.getClass().getName()));
  }

  /** This mimics a class inside of Spark which is private inside of LookupCatalog. */
  public static class CatalogAndIdentifier {
    private final CatalogPlugin catalog;
    private final Identifier identifier;

    public CatalogAndIdentifier(CatalogPlugin catalog, Identifier identifier) {
      this.catalog = catalog;
      this.identifier = identifier;
    }

    public CatalogAndIdentifier(Pair<CatalogPlugin, Identifier> identifier) {
      this.catalog = identifier.first();
      this.identifier = identifier.second();

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Set spark.sql.catalog.<name> to a TableCatalog implementation (e.g. org.apache.iceberg.spark.SparkCatalog or SparkSessionCatalog)
  2. Verify the catalog class with: spark.sessionState().catalogManager().catalog(name) instanceof TableCatalog before use
  3. Use the correct catalog name in the identifier — the current name resolves to the wrong plugin

Example fix

// before
spark.conf.set("spark.sql.catalog.my_cat", "org.apache.spark.sql.catalog.MyFuncCatalog");
// after
spark.conf.set("spark.sql.catalog.my_cat", "org.apache.iceberg.spark.SparkCatalog");
Defensive patterns

Strategy: type-guard

Validate before calling

CatalogPlugin cat = catalogManager.catalog(name);
if (!(cat instanceof TableCatalog)) { throw new IllegalStateException(name + " is not a TableCatalog"); }

Type guard

if (catalog instanceof TableCatalog) { /* safe to use */ }

Try / catch

try { return Spark3Util.catalogAndIdentifier(spark, ident); } catch (IllegalArgumentException e) { if (e.getMessage().contains("not a TableCatalog")) { /* fix catalog config */ } throw e; }

Prevention

When it happens

Trigger: Resolving a table identifier whose first part names a catalog that is not a TableCatalog — e.g. pointing Iceberg procedures/ALTER TABLE at a catalog registered as CatalogPlugin but not implementing TableCatalog.

Common situations: Misconfigured spark.sql.catalog.<name> entries where the class implements only CatalogPlugin; using session catalog defaults that resolve to a non-table catalog.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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