apache/iceberg · error · UnsupportedOperationException

startsWith expression is not supported by the visitor

Error message

startsWith expression is not supported by the visitor

What it means

The base visitor's default startsWith(BoundReference, Literal) throws UnsupportedOperationException; string-prefix predicates must be explicitly supported by a visitor. Thrown when a STARTS_WITH predicate reaches a visitor that does not override this method.

Source

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

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

    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

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Override startsWith(BoundReference<T> ref, Literal<T> lit) in the visitor to evaluate/translate the prefix predicate
  2. Catch UnsupportedOperationException and fall back to a conservative evaluation or reject the filter
  3. Upgrade the visitor/evaluator implementation to a version supporting startsWith

Example fix

// before
// no startsWith override -> default throws
// after
@Override
public <T> R startsWith(BoundReference<T> ref, Literal<T> lit) {
  String prefix = (String) lit.value();
  return evaluatePrefix(ref.column(), prefix);
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean hasStartsWith = exprOpScan(expr, op -> op == Expression.Operation.STARTS_WITH);

Type guard

static boolean isStartsWith(Predicate p) { return p.op() == Expression.Operation.STARTS_WITH; }

Try / catch

try {
  return visitor.startsWith(ref, lit);
} catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("startsWith expression is not supported")) {
    return conservativeResult(); // e.g. rows=true for evaluators
  }
  throw e;
}

Prevention

When it happens

Trigger: Visiting an expression built with Expressions.startsWith(col, "prefix") with a visitor lacking a startsWith override, e.g. custom evaluators, older summary evaluators, or engines' pushdown filters hitting an un-updated visitor.

Common situations: Pushing LIKE 'abc%' style filters into custom visitors; third-party catalogs/evaluators written before the startsWith predicate existed.

Related errors


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