{"record":{"id":"21ee2aefd0f56794","repo":"apache/beam","slug":"table-of-class-does-not-implement-hastableoperations","errorCode":null,"errorMessage":"Table {} of class {} does not implement HasTableOperations","messagePattern":"Table (.+?) of class (.+?) does not implement HasTableOperations","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableTableSpec.java","lineNumber":325,"sourceCode":"   */\n  public static SerializableTableSpec fromTable(Table table) {\n    return fromTable(table.name(), table);\n  }\n\n  /**\n   * Constructs a {@link SerializableTableSpec} from a {@link TableIdentifier} and a {@link Table}.\n   */\n  public static SerializableTableSpec fromTable(TableIdentifier tableIdentifier, Table table) {\n    return fromTable(IcebergUtils.tableIdentifierToString(tableIdentifier), table);\n  }\n\n  /**\n   * Constructs a {@link SerializableTableSpec} from an explicit table identifier string and a\n   * {@link Table}.\n   */\n  public static SerializableTableSpec fromTable(String tableIdentifierString, Table table) {\n    if (!(table instanceof HasTableOperations)) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Table %s of class %s does not implement HasTableOperations\",\n              table.name(), table.getClass().getName()));\n    }\n\n    TableMetadata metadata = ((HasTableOperations) table).operations().current();\n    long lastUpdatedMillis = metadata != null ? metadata.lastUpdatedMillis() : 0L;\n    List<String> encryptedKeyJsons = Collections.emptyList();\n    if (metadata != null && metadata.encryptionKeys() != null) {\n      encryptedKeyJsons =\n          metadata.encryptionKeys().stream()\n              .map(key -> EncryptedKeyParser.toJson(key, false))\n              .collect(Collectors.toList());\n    }\n\n    ImmutableMap.Builder<Integer, String> schemasJson = ImmutableMap.builder();\n    for (Map.Entry<Integer, Schema> entry : table.schemas().entrySet()) {\n      schemasJson.put(entry.getKey(), SchemaParser.toJson(entry.getValue()));","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/SerializableTableSpec.java#L307-L343","documentation":"SerializableTableSpec.fromTable requires the Iceberg Table to expose its underlying TableOperations so metadata can be serialized. If the Table instance does not implement HasTableOperations, the spec cannot capture the table's current metadata, so an IllegalArgumentException is thrown. Custom or wrapper Table implementations that do not delegate to a real Iceberg table trigger this.","triggerScenarios":"Calling SerializableTableSpec.fromTable(identifier, table) with a Table implementation that is not a HasTableOperations — e.g. a custom wrapper, a mock, or a non-standard catalog-returned table.","commonSituations":"Wrapping or decorating Iceberg tables in custom catalogs; passing test doubles/mocks into Beam Iceberg IO; using a Table adapter from another Iceberg version or shim layer that lacks HasTableOperations.","solutions":["Pass the actual Table obtained from an Iceberg Catalog (e.g. catalog.loadTable(identifier)), which implements HasTableOperations.","If you have a wrapper, unwrap it to the underlying Iceberg table before calling fromTable.","Implement HasTableOperations (and operations()) in your custom Table class, returning a real TableOperations.","Verify the org.apache.iceberg Table import used by your class matches the Beam pipeline's Iceberg version."],"exampleFix":"// before\nTable wrapped = new MyTableDecorator(catalog.loadTable(id));\nSerializableTableSpec spec = SerializableTableSpec.fromTable(id, wrapped); // throws\n\n// after\nTable table = catalog.loadTable(id);\nSerializableTableSpec spec = SerializableTableSpec.fromTable(id, table);","handlingStrategy":"validation","validationCode":"if (!(table instanceof org.apache.iceberg.HasTableOperations)) {\n  throw new IllegalArgumentException(\n      \"fromTable requires a catalog-backed Iceberg Table implementing HasTableOperations, got: \"\n          + table.getClass().getName());\n}","typeGuard":"boolean isSerializable = table instanceof org.apache.iceberg.HasTableOperations;","tryCatchPattern":"try {\n  spec = SerializableTableSpec.fromTable(id, table);\n} catch (IllegalArgumentException e) {\n  if (!e.getMessage().contains(\"does not implement HasTableOperations\")) throw e;\n  // fall back to catalog.loadTable(id)\n}","preventionTips":["Always pass tables obtained directly from an Iceberg Catalog, not wrappers or mocks.","Unwrap decorator Table implementations before constructing a SerializableTableSpec.","Keep the Iceberg dependency version aligned with the one Beam's iceberg IO expects."],"tags":["java","iceberg","beam","illegal-argument"],"backgroundTag":"incompatible-source-type","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}