apache/iceberg · error · UnsupportedOperationException

notStartsWith expression is not supported by the visitor

Error message

notStartsWith expression is not supported by the visitor

What it means

The base visitor's default notStartsWith(BoundReference, Literal) throws UnsupportedOperationException; NOT STARTS_WITH predicates are only handled by visitors that override this method. Thrown when a notStartsWith predicate is visited without an override.

Source

Thrown at api/src/main/java/org/apache/iceberg/expressions/ExpressionVisitors.java:125

    public <T> R notEq(BoundReference<T> ref, Literal<T> lit) {
      return null;
    }

    public <T> R in(BoundReference<T> ref, Set<T> literalSet) {
      throw new UnsupportedOperationException("In expression is not supported by the visitor");
    }

    public <T> R notIn(BoundReference<T> ref, Set<T> literalSet) {
      throw new UnsupportedOperationException("notIn expression is not supported by the visitor");
    }

    public <T> R startsWith(BoundReference<T> ref, Literal<T> lit) {
      throw new UnsupportedOperationException(
          "startsWith expression is not supported by the visitor");
    }

    public <T> R notStartsWith(BoundReference<T> ref, Literal<T> lit) {
      throw new UnsupportedOperationException(
          "notStartsWith expression is not supported by the visitor");
    }

    /**
     * Handle a non-reference value in this visitor.
     *
     * <p>Visitors that require {@link BoundReference references} and not {@link Bound terms} can
     * use this method to return a default value for expressions with non-references. The default
     * implementation will throw a validation exception because the non-reference is not supported.
     *
     * @param term a non-reference bound expression
     * @param <T> a Java return type
     * @return a return value for the visitor
     */
    public <T> R handleNonReference(Bound<T> term) {
      throw new ValidationException("Visitor %s does not support non-reference: %s", this, term);
    }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Override notStartsWith(BoundReference<T> ref, Literal<T> lit) in the visitor (typically the negation of the startsWith result)
  2. Rewrite the expression with Expressions.rewriteNot so notStartsWith is expressed via supported forms, or fall back conservatively
  3. Upgrade the evaluator to a version that supports notStartsWith

Example fix

// before
// no notStartsWith override -> default throws
// after
@Override
public <T> R notStartsWith(BoundReference<T> ref, Literal<T> lit) {
  return not(startsWithResult(ref, lit));
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean hasNotStartsWith = exprOpScan(expr, op -> op == Expression.Operation.NOT_STARTS_WITH);

Type guard

static boolean isNotStartsWith(Predicate p) { return p.op() == Expression.Operation.NOT_STARTS_WITH; }

Try / catch

try {
  return visitor.notStartsWith(ref, lit);
} catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("notStartsWith expression is not supported")) {
    return not(visitor.startsWith(ref, lit));
  }
  throw e;
}

Prevention

When it happens

Trigger: Visiting Expressions.notStartsWith(col, "prefix") with a visitor lacking a notStartsWith override — commonly custom evaluators or engine integrations predating the predicate.

Common situations: Pushing NOT LIKE 'abc%' filters into older visitor implementations; evaluators updated for startsWith but not its negation.

Related errors


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