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
- Pass the unbound expression (from ExpressionParser or the table's parsed filter) to the visitor, not a bound one
- Use ExpressionVisitors with a BoundExpressionVisitor subclass if you must evaluate bound predicates
- 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
- Render SQL from the parsed (unbound) expression, before any TableScan binding
- Keep a reference to the original unbound filter alongside bound scans
- Don't reuse DESCRIBE helpers on scan-time bound filters
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
- Cannot convert bound predicates to SQL
- Cannot convert predicate to SQL: %s
- Cannot convert term to SQL: %s
- Cannot convert unknown expression: ${expr}
- Invalid aggregate:
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/c577f68a37465fed.
Report an issue: GitHub.