apache/iceberg · error · UnsupportedOperationException
does not implement isNaN
Error message
${className} does not implement isNaN What it means
BoundExpressionVisitor.isNaN has no default no-op; the base class throws naming the visitor class that failed to implement it, forcing subclasses to consciously handle NaN checks (important for float/double columns).
Solutions
- Override isNaN(BoundReference) (and notNaN) in your visitor subclass
- Extend a higher-level visitor (e.g. ExpressionVisitors.CustomOrderExpressionVisitor or evaluator visitors) that already implements NaN handling
- Recompile/refresh subclasses after upgrading Iceberg so newly added required methods are implemented
Example fix
// before
class MyVisitor<R> extends ExpressionVisitors.BoundExpressionVisitor<R> {
@Override public <T> R isNull(BoundReference<T> ref) { return handleNull(ref); }
}
// after
class MyVisitor<R> extends ExpressionVisitors.BoundExpressionVisitor<R> {
@Override public <T> R isNull(BoundReference<T> ref) { return handleNull(ref); }
@Override public <T> R isNaN(BoundReference<T> ref) { return handleNaN(ref); }
@Override public <T> R notNaN(BoundReference<T> ref) { return handleNotNaN(ref); }
} Defensive patterns
Strategy: validation
Validate before calling
Set<java.lang.reflect.Method> m = Set.of(visitor.getClass().getMethods());
if (!m.contains(visitor.getClass().getMethod("isNaN", BoundReference.class))) {
throw new IllegalArgumentException("visitor must override isNaN");
} Try / catch
try { return visitPredicate(pred, visitor); }
catch (UnsupportedOperationException e) { throw new IllegalStateException("Visitor " + visitor.getClass() + " must implement isNaN/notNaN", e); } Prevention
- Always override isNaN and notNaN when extending BoundExpressionVisitor
- Handle NaN for floating-point columns explicitly
- Recompile subclasses against new Iceberg API versions
When it happens
Trigger: Evaluating or visiting an isNaN(floatCol) predicate with a BoundExpressionVisitor subclass that overrides other predicate methods but not isNaN — typically during Bind→visit of row filters containing NaN checks.
Common situations: Custom visitors written before isNaN was added to the API (older subclass compiled against/behaving like the older base); filter visitors that handled isNull/notNull but forgot NaN cases on floating-point columns.
Related errors
- Cannot visit aggregate expression
- AboveMax has no comparator
- BelowMin has no comparator
- BelowMin has no value
- Cannot change the type of BelowMin
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/0564bed80220853e.
Report an issue: GitHub.
Appendix: source
Thrown at api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java:78
throw new UnsupportedOperationException("Cannot visit aggregate expression");
}
public <T> R aggregate(UnboundAggregate<T> agg) {
throw new UnsupportedOperationException("Cannot visit aggregate expression");
}
}
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;View on GitHub (pinned to 86d9c8fc54)