apache/iceberg · error · UnsupportedOperationException

Cannot convert bound predicates to SQL

Error message

Cannot convert bound predicates to SQL

What it means

Spark3Util.DescribeExpressionVisitor extends the unbound expression visitor; its predicate(BoundPredicate) override unconditionally throws UnsupportedOperationException because DESCRIBE TABLE works on unbound (parsed) Iceberg expressions and cannot render bound predicates that depend on table data/statistics.

Source

Thrown at spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:651

    @Override
    public String not(String result) {
      return "NOT (" + result + ")";
    }

    @Override
    public String and(String leftResult, String rightResult) {
      return "(" + leftResult + " AND " + rightResult + ")";
    }

    @Override
    public String or(String leftResult, String rightResult) {
      return "(" + leftResult + " OR " + rightResult + ")";
    }

    @Override
    public <T> String predicate(BoundPredicate<T> pred) {
      throw new UnsupportedOperationException("Cannot convert bound predicates to SQL");
    }

    @Override
    public <T> String predicate(UnboundPredicate<T> pred) {
      switch (pred.op()) {
        case IS_NULL:
          return sqlString(pred.term()) + " IS NULL";
        case NOT_NULL:
          return sqlString(pred.term()) + " IS NOT NULL";
        case IS_NAN:
          return "is_nan(" + sqlString(pred.term()) + ")";
        case NOT_NAN:
          return "not_nan(" + sqlString(pred.term()) + ")";
        case LT:
          return sqlString(pred.term()) + " < " + sqlString(pred.literal());
        case LT_EQ:
          return sqlString(pred.term()) + " <= " + sqlString(pred.literal());
        case GT:

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Pass the unbound expression (from ExpressionParser or the table's parsed filter) to the visitor, not a bound one
  2. Use ExpressionVisitors with a BoundExpressionVisitor subclass if you must evaluate bound predicates
  3. Render SQL from the unbound predicate via Expressions.serialize or build a custom unbound visitor

Example fix

// before
visitor.predicate(boundPred); // throws
// after
Expression unbound = Expressions.alwaysTrue(); // reconstruct or keep original unbound expr
visitor.predicate((UnboundPredicate<?>) unbound);
Defensive patterns

Strategy: type-guard

Validate before calling

if (expr instanceof org.apache.iceberg.expressions.BoundExpression) {
  throw new IllegalArgumentException("DescribeExpressionVisitor requires unbound expressions");
}

Type guard

boolean isUnbound(org.apache.iceberg.expressions.Expression e) {
  return !(e instanceof org.apache.iceberg.expressions.BoundExpression);
}

Try / catch

try {
  return visitor.describe(expr);
} catch (UnsupportedOperationException e) {
  throw new IllegalStateException("Bind-free expression required for SQL rendering", e);
}

Prevention

When it happens

Trigger: Calling DescribeExpressionVisitor (via Spark3Util describe/predicate helpers) with a BoundExpression — e.g. passing an already-bound row-filter from a scan instead of the unbound parsed expression.

Common situations: Custom code that reuses the describe visitor on expressions obtained from Iceberg scan/filter APIs after binding; mixing bound/unbound expression APIs in tooling.

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/c577f68a37465fed. Report an issue: GitHub.