{"record":{"id":"f870d21268bdcc3f","repo":"apache/iceberg","slug":"cannot-visit-aggregate-expression","errorCode":null,"errorMessage":"Cannot visit aggregate expression","messagePattern":"Cannot visit aggregate expression","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java","lineNumber":60,"sourceCode":"\n    public R and(R leftResult, R rightResult) {\n      return null;\n    }\n\n    public R or(R leftResult, R rightResult) {\n      return null;\n    }\n\n    public <T> R predicate(BoundPredicate<T> pred) {\n      return null;\n    }\n\n    public <T> R predicate(UnboundPredicate<T> pred) {\n      return null;\n    }\n\n    public <T, C> R aggregate(BoundAggregate<T, C> agg) {\n      throw new UnsupportedOperationException(\"Cannot visit aggregate expression\");\n    }\n\n    public <T> R aggregate(UnboundAggregate<T> agg) {\n      throw new UnsupportedOperationException(\"Cannot visit aggregate expression\");\n    }\n  }\n\n  public abstract static class BoundExpressionVisitor<R> extends ExpressionVisitor<R> {\n    public <T> R isNull(BoundReference<T> ref) {\n      return null;\n    }\n\n    public <T> R notNull(BoundReference<T> ref) {\n      return null;\n    }\n\n    public <T> R isNaN(BoundReference<T> ref) {\n      throw new UnsupportedOperationException(","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java#L42-L78","documentation":"ExpressionVisitor's default aggregate(BoundAggregate) implementation throws because plain expression visitors are not designed to handle aggregates (count, sum, max). Subclasses must override the aggregate methods to support them.","triggerScenarios":"Visiting an expression tree containing a BoundAggregate with a visitor class that does not override aggregate(BoundAggregate), e.g. via ExpressionVisitors.visit(agg, visitor).","commonSituations":"Using an ExpressionVisitor built for filters (Evaluator-style visitors) on aggregate expressions; metrics/aggregation code reusing a filter visitor; library code visiting rewrite plans containing aggregates.","solutions":["Use a visitor class that overrides aggregate(BoundAggregate), e.g. extend ExpressionVisitors.AggregateVisitor","Override aggregate(BoundAggregate) (and aggregate(UnboundAggregate)) in your visitor subclass","Route aggregate expressions to aggregate-specific APIs instead of generic ExpressionVisitors.visit"],"exampleFix":"// before\nExpressionVisitors.visit(boundAggregate, new ExpressionVisitors.ExpressionVisitor<Object>() {});\n// after\nExpressionVisitors.visit(boundAggregate, new ExpressionVisitors.AggregateVisitor<Object>() {\n  @Override\n  public <T, C> Object aggregate(BoundAggregate<T, C> agg) { return handleAgg(agg); }\n});","handlingStrategy":"type-guard","validationCode":"if (expr instanceof BoundAggregate && !(visitor instanceof ExpressionVisitors.AggregateVisitor)) {\n  throw new IllegalArgumentException(\"use an AggregateVisitor for aggregate expressions\");\n}","typeGuard":"boolean canVisitAggregates(ExpressionVisitor<?> v) {\n  return v instanceof ExpressionVisitors.AggregateVisitor;\n}","tryCatchPattern":"try { return ExpressionVisitors.visit(boundAgg, visitor); }\ncatch (UnsupportedOperationException e) { throw new IllegalStateException(\"Visitor does not support aggregates; use AggregateVisitor\", e); }","preventionTips":["Never reuse filter visitors on aggregate expressions","Override both aggregate(BoundAggregate) and aggregate(UnboundAggregate)","Check expression kind before dispatching to generic visit"],"tags":["expressions","visitor-pattern","aggregates"],"backgroundTag":"method-not-implemented","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}