{"record":{"id":"e24e4367298ad170","repo":"apache/iceberg","slug":"year-transform-is-not-supported","errorCode":null,"errorMessage":"Year transform is not supported","messagePattern":"Year transform is not supported","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/transforms/PartitionSpecVisitor.java","lineNumber":57,"sourceCode":"\n  default T bucket(String sourceName, int sourceId, int numBuckets) {\n    throw new UnsupportedOperationException(\"Bucket transform is not supported\");\n  }\n\n  default T truncate(int fieldId, String sourceName, int sourceId, int width) {\n    return truncate(sourceName, sourceId, width);\n  }\n\n  default T truncate(String sourceName, int sourceId, int width) {\n    throw new UnsupportedOperationException(\"Truncate transform is not supported\");\n  }\n\n  default T year(int fieldId, String sourceName, int sourceId) {\n    return year(sourceName, sourceId);\n  }\n\n  default T year(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Year transform is not supported\");\n  }\n\n  default T month(int fieldId, String sourceName, int sourceId) {\n    return month(sourceName, sourceId);\n  }\n\n  default T month(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Month transform is not supported\");\n  }\n\n  default T day(int fieldId, String sourceName, int sourceId) {\n    return day(sourceName, sourceId);\n  }\n\n  default T day(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Day transform is not supported\");\n  }\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/transforms/PartitionSpecVisitor.java#L39-L75","documentation":"PartitionSpecVisitor's default year() methods throw UnsupportedOperationException because a visitor that does not override year() cannot handle partition fields using the year temporal transform. The visitor dispatch (PartitionSpecVisitor.visit) detects a year transform (Dates.YEAR, Timestamps.MICROS_TO_YEAR, NANOS_TO_YEAR, or Years) and routes to year(); if the concrete visitor only implements a subset of transforms, the default implementation fails. This is an intentional fail-fast so unsupported transforms surface instead of silently producing wrong output.","triggerScenarios":"Calling PartitionSpecVisitor.visit(spec, visitor) or visit(schema, field, visitor) on a table whose partition spec contains a year(...) partition field while the concrete visitor implementation has not overridden year(int, String, int) or year(String, int).","commonSituations":"Custom visitor implementations (e.g. metadata translators to other catalog formats like Hive, JDBC, or Nessie) written before year/month/day/hour overrides existed, applied to tables partitioned by years(ts). Also common after upgrading Iceberg and applying a visitor to an old table spec that uses temporal partitioning.","solutions":["Override year(int fieldId, String sourceName, int sourceId) in your PartitionSpecVisitor implementation to handle the year transform","If the visitor genuinely cannot support year partitioning, override year() to return null or throw a clearer domain-specific error instead of relying on the default","Check the table's spec with spec.fields() and spec.partitionType() before running the visitor to confirm which transforms it uses"],"exampleFix":"// before\nPartitionSpecVisitor<String> visitor = new PartitionSpecVisitor<String>() {\n  @Override\n  public String identity(int fieldId, String sourceName, int sourceId) { return sourceName; }\n};\n// after\nPartitionSpecVisitor<String> visitor = new PartitionSpecVisitor<String>() {\n  @Override\n  public String identity(int fieldId, String sourceName, int sourceId) { return sourceName; }\n\n  @Override\n  public String year(int fieldId, String sourceName, int sourceId) {\n    return \"years(\" + sourceName + \")\";\n  }\n};","handlingStrategy":"validation","validationCode":"boolean hasUnsupportedTransform(PartitionSpec spec) {\n  return spec.fields().stream().anyMatch(f -> f.transform() instanceof Years\n      || f.transform() == Dates.YEAR || f.transform() == Timestamps.MICROS_TO_YEAR);\n}\n// call before PartitionSpecVisitor.visit and fail with your own message if true","typeGuard":"if (field.transform() instanceof Years || field.transform() == Dates.YEAR) {\n  throw new IllegalStateException(\"Visitor does not support year transform: \" + field);\n}","tryCatchPattern":"try {\n  results = PartitionSpecVisitor.visit(spec, visitor);\n} catch (UnsupportedOperationException e) {\n  throw new IllegalArgumentException(\"Spec uses transforms this visitor does not support: \" + e.getMessage(), e);\n}","preventionTips":["Override every hook your source tables can use: identity, bucket, truncate, year, month, day, hour, alwaysNull, unknown","Test visitors against specs using each Iceberg transform, including temporal ones","Check spec.partitionType() before visiting to enumerate required transform support"],"tags":["java","partitioning","visitor-pattern","unsupported-transform"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}