apache/iceberg · error · UnsupportedOperationException
does not implement notNaN
Error message
${className} does not implement notNaN What it means
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.
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
Example fix
// before
class MyVisitor<T> extends ExpressionVisitors.CustomOrderExpressionVisitor<T> { /* no notNaN */ }
// after
class MyVisitor<T> extends ExpressionVisitors.CustomOrderExpressionVisitor<T> {
@Override
public <F> T notNaN(BoundReference<F> ref) { return handleNaN(ref); }
@Override
public <F> T isNaN(BoundReference<F> ref) { return handleNaN(ref); }
} Defensive patterns
Strategy: try-catch
Validate before calling
// detect NaN predicates in the expression before visiting
boolean hasNaN = new ExpressionVisitors.ExpressionVisitor<Boolean>() {
@Override public Boolean predicate(UnboundPredicate<?> p) {
return p.op() == Expression.Operation.IS_NAN || p.op() == Expression.Operation.NOT_NAN;
}
@Override public Boolean alwaysTrue() { return false; }
@Override public Boolean alwaysFalse() { return false; }
@Override public Boolean not(Boolean r) { return r; }
@Override public Boolean and(Boolean l, Boolean r) { return l || r; }
@Override public Boolean or(Boolean l, Boolean r) { return l || r; }
}.visit(expr); Type guard
boolean supportsNotNaN(ExpressionVisitor<?> v) {
try { v.getClass().getDeclaredMethod("notNaN", BoundReference.class); return true; }
catch (NoSuchMethodException e) { return false; }
} Try / catch
try {
return visitPredicate(pred, visitor);
} catch (UnsupportedOperationException e) {
if (e.getMessage().endsWith("does not implement notNaN")) {
return conservativeResult(); // e.g. return rows
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Bucket transform is not supported
- Identity transform is not supported
- In expression is not supported by the visitor
- notIn expression is not supported by the visitor
- notStartsWith expression is not supported by the visitor
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/15898ad2e10253c2.
Report an issue: GitHub.
Appendix: source
Thrown at api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java:83
}
}
public abstract static class BoundExpressionVisitor<R> extends ExpressionVisitor<R> {
public <T> R isNull(BoundReference<T> ref) {
return null;
}
public <T> R notNull(BoundReference<T> ref) {
return null;
}
public <T> R isNaN(BoundReference<T> ref) {
throw new UnsupportedOperationException(
this.getClass().getName() + " does not implement isNaN");
}
public <T> R notNaN(BoundReference<T> ref) {
throw new UnsupportedOperationException(
this.getClass().getName() + " does not implement notNaN");
}
public <T> R lt(BoundReference<T> ref, Literal<T> lit) {
return null;
}
public <T> R ltEq(BoundReference<T> ref, Literal<T> lit) {
return null;
}
public <T> R gt(BoundReference<T> ref, Literal<T> lit) {
return null;
}
public <T> R gtEq(BoundReference<T> ref, Literal<T> lit) {
return null;
}View on GitHub (pinned to 86d9c8fc54)