{"record":{"id":"eb9bbbf7f2efd024","repo":"apache/iceberg","slug":"unknown-spark-table-type-s","errorCode":null,"errorMessage":"Unknown Spark table type: %s","messagePattern":"Unknown Spark table type: (.+?)","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/SparkCatalog.java","lineNumber":214,"sourceCode":"      } catch (NumberFormatException e) {\n        SnapshotRef ref = sparkTable.table().refs().get(version);\n        ValidationException.check(\n            ref != null,\n            \"Cannot find matching snapshot ID or reference name for version %s\",\n            version);\n\n        if (ref.isBranch()) {\n          return sparkTable.copyWithBranch(version);\n        } else {\n          return sparkTable.copyWithSnapshotId(ref.snapshotId());\n        }\n      }\n\n    } else if (table instanceof SparkChangelogTable) {\n      throw new UnsupportedOperationException(\"AS OF is not supported for changelogs\");\n\n    } else {\n      throw new IllegalArgumentException(\"Unknown Spark table type: \" + table.getClass().getName());\n    }\n  }\n\n  @Override\n  public Table loadTable(Identifier ident, long timestamp) throws NoSuchTableException {\n    Table table = loadTable(ident);\n\n    if (table instanceof SparkTable) {\n      SparkTable sparkTable = (SparkTable) table;\n\n      Preconditions.checkArgument(\n          sparkTable.snapshotId() == null && sparkTable.branch() == null,\n          \"Cannot do time-travel based on both table identifier and AS OF\");\n\n      // convert the timestamp to milliseconds as Spark passes microseconds\n      // but Iceberg uses milliseconds for snapshot timestamps\n      long timestampMillis = TimeUnit.MICROSECONDS.toMillis(timestamp);\n      long snapshotId = SnapshotUtil.snapshotIdAsOfTime(sparkTable.table(), timestampMillis);","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/SparkCatalog.java#L196-L232","documentation":"SparkCatalog.loadTable(ident, version) handles a closed set of SparkTable subtypes (regular SparkTable, SparkChangelogTable). If the loaded table object is neither, the method throws IllegalArgumentException 'Unknown Spark table type: <class name>'. This indicates the table implementation was swapped by an extension or wrapper the catalog does not recognize.","triggerScenarios":"loadTable(ident, version) where the resolved Table is a custom/extension subclass of SparkTable (e.g. from a third-party catalog plugin or instrumentation wrapper) that is not SparkTable or SparkChangelogTable.","commonSituations":"Using Iceberg extensions or vendor plugins that wrap SparkTable; classpath mixing of different Iceberg versions producing unexpected table classes; custom catalog returning its own Table implementation through a path that reaches SparkCatalog's time-travel loader.","solutions":["Ensure the catalog returns vanilla org.apache.iceberg.spark.SparkTable for time-travel loads; unwrap or register support for the custom type.","Align Iceberg jar versions across the cluster (no mixed iceberg-spark-runtime versions on the classpath).","Update the Iceberg extension/plugin to a version compatible with this SparkCatalog implementation.","As a workaround, do time travel via DataFrame reader options (snapshot-id / as-of-timestamp) instead of the versioned loadTable API."],"exampleFix":"// before\nTable t = sparkCatalog.loadTable(ident, version); // custom wrapper type -> IllegalArgumentException\n\n// after\nDataset<Row> df = spark.read().format(\"iceberg\")\n    .option(\"snapshot-id\", version)\n    .load(\"db.tbl\"); // bypasses type dispatch","handlingStrategy":"try-catch","validationCode":"Table t = sparkCatalog.loadTable(ident);\nboolean supported = t instanceof org.apache.iceberg.spark.SparkTable\n    || t instanceof org.apache.iceberg.spark.SparkChangelogTable;\nif (!supported) {\n  throw new IllegalStateException(\"Unsupported table implementation for time travel: \" + t.getClass().getName());\n}","typeGuard":"boolean isVersionedLoadSupported = t instanceof org.apache.iceberg.spark.SparkTable;","tryCatchPattern":"try {\n  return sparkCatalog.loadTable(ident, version);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().startsWith(\"Unknown Spark table type\")) { /* fall back to snapshot-id read option */ }\n  throw e;\n}","preventionTips":["Pin a single iceberg-spark-runtime version on the classpath; avoid shaded duplicates.","Verify extension/plugin catalogs return vanilla SparkTable instances.","Prefer the DataFrame reader snapshot-id option for third-party table wrappers.","Test time travel against every custom catalog in your integration suite."],"tags":["spark","time-travel","type-dispatch","internal-invariant","extension"],"backgroundTag":"internal-invariant-violation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}