apache/iceberg · error · IllegalArgumentException

No negation for operation: ${operation}

Error message

No negation for operation: ${operation}

What it means

Operation.negate() returns the inverse operation (e.g. EQ -> NOT_EQ, IN -> NOT_IN). Some operations have no logical negation, so the default branch throws IllegalArgumentException listing the operation. Only invertible operations can be negated at the operation level.

Source

Thrown at api/src/main/java/org/apache/iceberg/expressions/Expression.java:95

          return Operation.GT;
        case GT:
          return Operation.LT_EQ;
        case GT_EQ:
          return Operation.LT;
        case EQ:
          return Operation.NOT_EQ;
        case NOT_EQ:
          return Operation.EQ;
        case IN:
          return Operation.NOT_IN;
        case NOT_IN:
          return Operation.IN;
        case STARTS_WITH:
          return Operation.NOT_STARTS_WITH;
        case NOT_STARTS_WITH:
          return Operation.STARTS_WITH;
        default:
          throw new IllegalArgumentException("No negation for operation: " + this);
      }
    }

    /** Returns the equivalent operation when the left and right operands are exchanged. */
    // Allow flipLR as a name because it's a public API
    @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
    public Operation flipLR() {
      switch (this) {
        case LT:
          return Operation.GT;
        case LT_EQ:
          return Operation.GT_EQ;
        case GT:
          return Operation.LT;
        case GT_EQ:
          return Operation.LT_EQ;
        case EQ:
          return Operation.EQ;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check whether the operation has a negation before calling negate() (e.g. switch on op)
  2. Wrap the expression in Expressions.not(expr) instead of relying on operation-level negation if the op is non-invertible
  3. Restrict negation to operations like EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ, IN, NOT_IN, IS_NULL, NOT_NULL, STARTS_WITH, NOT_STARTS_WITH

Example fix

// before
Operation negated = op.negate(); // throws for IS_NAN
// after
Expression negated = Expressions.not(expr); // safe generic negation
Defensive patterns

Strategy: validation

Validate before calling

Set<Operation> negatable = Set.of(EQ, NOT_EQ, LT, LT_EQ, GT, GT_EQ, IN, NOT_IN, IS_NULL, NOT_NULL, STARTS_WITH, NOT_STARTS_WITH); if (!negatable.contains(op)) { /* use Expressions.not(expr) instead */ }

Type guard

boolean hasNegation = !Set.of(Operation.IS_NAN, Operation.NOT_NAN).contains(op);

Try / catch

try { negated = op.negate(); } catch (IllegalArgumentException e) { negated = null; /* wrap with Expressions.not instead */ }

Prevention

When it happens

Trigger: Calling Operation.negate() on an operation without an inverse, such as IS_NAN/NOT_NAN or STARTS_WITH-family operations not covered by the switch, typically while building NOT(expr) via ExpressionParser or calling expr.negate()/not(...).

Common situations: Rewriting filters by pushing NOT down into predicates; users negating expressions containing operations that lack operation-level negation.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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