{"record":{"id":"15898ad2e10253c2","repo":"apache/iceberg","slug":"classname-does-not-implement-notnan","errorCode":null,"errorMessage":"${className} does not implement notNaN","messagePattern":"(.+?) does not implement notNaN","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java","lineNumber":83,"sourceCode":"    }\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(\n          this.getClass().getName() + \" does not implement isNaN\");\n    }\n\n    public <T> R notNaN(BoundReference<T> ref) {\n      throw new UnsupportedOperationException(\n          this.getClass().getName() + \" does not implement notNaN\");\n    }\n\n    public <T> R lt(BoundReference<T> ref, Literal<T> lit) {\n      return null;\n    }\n\n    public <T> R ltEq(BoundReference<T> ref, Literal<T> lit) {\n      return null;\n    }\n\n    public <T> R gt(BoundReference<T> ref, Literal<T> lit) {\n      return null;\n    }\n\n    public <T> R gtEq(BoundReference<T> ref, Literal<T> lit) {\n      return null;\n    }","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java#L65-L101","documentation":"ExpressionVisitors.CustomOrderExpressionVisitor (and its base classes) provide default implementations of isNaN/notNaN that throw UnsupportedOperationException. The library throws this when an expression visitor is asked to evaluate a notNaN (or isNaN) predicate but the concrete visitor subclass has not overridden the method. It signals that the visitor implementation does not cover this newer predicate kind.","triggerScenarios":"Calling ExpressionVisitors.visit(pred, visitor) or pred's accept path where pred is a BoundPredicate from notNaN(col) (or isNaN), and the visitor class in use does not override notNaN(BoundReference). Typically happens after upgrading Iceberg and running an older visitor (e.g. a custom metrics evaluator or rewrite) against expressions that now include NaN checks.","commonSituations":"Engine integrations (Spark/Flink) or third-party evaluators written against an older visitor API encountering newly added isNaN/notNaN predicates in user filters; custom visitors that only overrode eq/lt/etc.","solutions":["Override notNaN (and isNaN) in your visitor subclass to return an appropriate result for the NaN predicate","If the visitor genuinely cannot handle NaN predicates, catch UnsupportedOperationException and fall back to a conservative result (e.g. rows=true) or reject the expression","Upgrade to a visitor/evaluator implementation that supports NaN predicates"],"exampleFix":"// before\nclass MyVisitor<T> extends ExpressionVisitors.CustomOrderExpressionVisitor<T> { /* no notNaN */ }\n// after\nclass MyVisitor<T> extends ExpressionVisitors.CustomOrderExpressionVisitor<T> {\n  @Override\n  public <F> T notNaN(BoundReference<F> ref) { return handleNaN(ref); }\n  @Override\n  public <F> T isNaN(BoundReference<F> ref) { return handleNaN(ref); }\n}","handlingStrategy":"try-catch","validationCode":"// detect NaN predicates in the expression before visiting\nboolean hasNaN = new ExpressionVisitors.ExpressionVisitor<Boolean>() {\n  @Override public Boolean predicate(UnboundPredicate<?> p) {\n    return p.op() == Expression.Operation.IS_NAN || p.op() == Expression.Operation.NOT_NAN;\n  }\n  @Override public Boolean alwaysTrue() { return false; }\n  @Override public Boolean alwaysFalse() { return false; }\n  @Override public Boolean not(Boolean r) { return r; }\n  @Override public Boolean and(Boolean l, Boolean r) { return l || r; }\n  @Override public Boolean or(Boolean l, Boolean r) { return l || r; }\n}.visit(expr);","typeGuard":"boolean supportsNotNaN(ExpressionVisitor<?> v) {\n  try { v.getClass().getDeclaredMethod(\"notNaN\", BoundReference.class); return true; }\n  catch (NoSuchMethodException e) { return false; }\n}","tryCatchPattern":"try {\n  return visitPredicate(pred, visitor);\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().endsWith(\"does not implement notNaN\")) {\n    return conservativeResult(); // e.g. return rows\n  }\n  throw e;\n}","preventionTips":["Override both isNaN and notNaN whenever you override other double predicates in a custom visitor","After upgrading Iceberg, run your visitor over an expression set that includes isNaN/notNaN filters","Prefer extending ExpressionVisitors.CustomOrderExpressionVisitor and implement all predicate methods rather than relying on defaults"],"tags":["java","expressions","visitor","unsupported-operation"],"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-14T11:17:12.474Z"}