apache/iceberg · error · ValidationException

NotNaN cannot be used with a non-floating-point column

Error message

NotNaN cannot be used with a non-floating-point column

What it means

NOT_NAN is the negation of IS_NAN and is likewise only valid on float and double columns. Binding an UnboundPredicate with Operation.NOT_NAN against a term whose bound type is not floating-point throws this ValidationException.

Source

Thrown at api/src/main/java/org/apache/iceberg/expressions/UnboundPredicate.java:154

      case NOT_NULL:
        if (!boundTerm.producesNull()
            && allAncestorFieldsAreRequired(struct, boundTerm.ref().fieldId())) {
          return Expressions.alwaysTrue();
        } else if (boundTerm.type().equals(Types.UnknownType.get())) {
          return Expressions.alwaysFalse();
        }
        return new BoundUnaryPredicate<>(Operation.NOT_NULL, boundTerm);
      case IS_NAN:
        if (floatingType(boundTerm.type().typeId())) {
          return new BoundUnaryPredicate<>(Operation.IS_NAN, boundTerm);
        } else {
          throw new ValidationException("IsNaN cannot be used with a non-floating-point column");
        }
      case NOT_NAN:
        if (floatingType(boundTerm.type().typeId())) {
          return new BoundUnaryPredicate<>(Operation.NOT_NAN, boundTerm);
        } else {
          throw new ValidationException("NotNaN cannot be used with a non-floating-point column");
        }
      default:
        throw new ValidationException("Operation must be IS_NULL, NOT_NULL, IS_NAN, or NOT_NAN");
    }
  }

  private boolean allAncestorFieldsAreRequired(StructType struct, int fieldId) {
    return TypeUtil.ancestorFields(struct.asSchema(), fieldId).stream()
        .allMatch(Types.NestedField::isRequired);
  }

  private boolean floatingType(Type.TypeID typeID) {
    return Type.TypeID.DOUBLE.equals(typeID) || Type.TypeID.FLOAT.equals(typeID);
  }

  private Expression bindLiteralOperation(BoundTerm<T> boundTerm) {
    if (op() == Operation.STARTS_WITH || op() == Operation.NOT_STARTS_WITH) {
      ValidationException.check(

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Emit notNaN only for float/double columns; use notNull otherwise
  2. Validate the referenced field's typeId before constructing the predicate
  3. Update the filter definition after column type changes

Example fix

// before
Expression e = Expressions.notNaN("long_col");
// after
Type t = schema.findType("long_col");
Expression e = (t.typeId() == Type.TypeID.FLOAT || t.typeId() == Type.TypeID.DOUBLE)
    ? Expressions.notNaN("long_col")
    : Expressions.notNull("long_col");
Defensive patterns

Strategy: validation

Validate before calling

Type t = schema.findType("col");
if (!(t instanceof Types.FloatType) && !(t instanceof Types.DoubleType)) { /* use notNull instead */ }

Type guard

static boolean supportsNotNan(Type t) { return t.typeId() == Type.TypeID.FLOAT || t.typeId() == Type.TypeID.DOUBLE; }

Try / catch

try { Expression bound = Binder.bind(struct, Expressions.notNaN("col"), true); } catch (ValidationException e) { /* substitute Expressions.notNull("col") */ }

Prevention

When it happens

Trigger: Building Expressions.notNaN("col") where col resolves to int/long/decimal/string/etc., then binding the expression to a Types.StructType (e.g. via table.filter or Binder.bind).

Common situations: Generic filter UIs or SQL translators that emit NOT NaN for any numeric column; stale filters after a column type changed from double to decimal/long.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/88ab5e2cf21f0fc4. Report an issue: GitHub.