apache/iceberg · error · IllegalArgumentException

%s does not have a format version

Error message

%s does not have a format version

What it means

TableUtil.formatVersion(Table) extracts the format version from the table's operations, but only for tables whose operations implement HasTableOperations or that are BaseMetadataTable instances. Any other Table implementation has no accessible operations/current metadata, so IllegalArgumentException is thrown. It's a guard against asking a non-standard table wrapper (e.g. mocks, static tables, custom wrappers) for a version it cannot provide.

Source

Thrown at core/src/main/java/org/apache/iceberg/TableUtil.java:40

public class TableUtil {
  private TableUtil() {}

  /** Returns the format version of the given table */
  public static int formatVersion(Table table) {
    Preconditions.checkArgument(null != table, "Invalid table: null");

    if (table instanceof SerializableTable) {
      SerializableTable serializableTable = (SerializableTable) table;
      return serializableTable.formatVersion();
    } else if (table instanceof HasTableOperations) {
      HasTableOperations ops = (HasTableOperations) table;
      return ops.operations().current().formatVersion();
    } else if (table instanceof BaseMetadataTable) {
      BaseMetadataTable metadataTable = (BaseMetadataTable) table;
      return metadataTable.table().operations().current().formatVersion();
    } else {
      throw new IllegalArgumentException(
          String.format("%s does not have a format version", table.getClass().getSimpleName()));
    }
  }

  /** Returns the metadata file location of the given table */
  public static String metadataFileLocation(Table table) {
    Preconditions.checkArgument(null != table, "Invalid table: null");

    if (table instanceof SerializableTable) {
      SerializableTable serializableTable = (SerializableTable) table;
      return serializableTable.metadataFileLocation();
    } else if (table instanceof HasTableOperations) {
      HasTableOperations ops = (HasTableOperations) table;
      return ops.operations().current().metadataFileLocation();
    } else if (table instanceof BaseMetadataTable) {
      return ((BaseMetadataTable) table).table().operations().current().metadataFileLocation();
    } else {
      throw new IllegalArgumentException(

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Ensure the Table handle comes from a catalog/BaseTable so operations() implements HasTableOperations
  2. Use TableUtil.formatVersion only on real base tables; unwrap metadata-table wrappers via .table() first
  3. Check supportsRowLineage/similar callers and exclude static/custom tables before calling
  4. If you own a Table wrapper, implement HasTableOperations so TableUtil can read the metadata

Example fix

// before
int version = TableUtil.formatVersion(staticTable); // throws
// after
Table base = (staticTable instanceof BaseMetadataTable)
    ? ((BaseMetadataTable) staticTable).table() : staticTable;
int version = TableUtil.formatVersion(base);
Defensive patterns

Strategy: type-guard

Validate before calling

boolean canReadFormatVersion(Table t) {
  return t instanceof BaseMetadataTable
      || (t.operations() instanceof HasTableOperations);
}

Type guard

Integer safeFormatVersion(Table t) {
  if (t instanceof BaseMetadataTable) t = ((BaseMetadataTable) t).table();
  return (t.operations() instanceof HasTableOperations)
      ? ((HasTableOperations) t.operations()).operations() == null ? null
        : ((HasTableOperations) t).operations().current().formatVersion()
      : null;
}

Try / catch

try { v = TableUtil.formatVersion(table); }
catch (IllegalArgumentException e) { v = null; /* non-standard table impl */ }

Prevention

When it happens

Trigger: TableUtil.formatVersion(table) (directly or via TableUtil.supportsRowLineage) with a Table that is neither HasTableOperations nor BaseMetadataTable — e.g. StaticTable, a custom Table decorator, or a test mock.

Common situations: Code that inspects format version to gate v2+ features (row lineage, deletes) run against static or wrapped tables; library users passing engine-adapter table wrappers into TableUtil helpers; version-upgrade scenarios where a Table implementation stopped implementing HasTableOperations.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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