apache/iceberg · error · java.lang.UnsupportedOperationException
Cannot convert bound predicates to SQL
Error message
Cannot convert bound predicates to SQL
What it means
DescribeExpressionVisitor explicitly rejects BoundPredicate: SQL rendering is only implemented for unbound predicates, so visiting an already-bound predicate (with bound terms/literals resolved against a schema) throws UnsupportedOperationException.
Source
Thrown at spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/Spark3Util.java:647
@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
- Convert the bound predicate back to unbound first (e.g. rebuild via Expressions util from the predicate's op/term/literal) before describing
- Use the unbound Expression (the original user filter) with Spark3Util.describe instead of the bound variant
- Handle bound predicates yourself with a custom ExpressionVisitor that renders BoundPredicate
Example fix
// before
Expression bound = Binder.bind(table.schema(), Expressions.equal("id", 1));
String sql = Spark3Util.describe(bound); // throws
// after
Expression unbound = Expressions.equal("id", 1);
String sql = Spark3Util.describe(unbound); Defensive patterns
Strategy: try-catch
Validate before calling
if (expr instanceof BoundPredicate || hasBoundChildren(expr)) { /* convert to unbound first */ } Type guard
boolean isUnbound(Expression e) { return !(e instanceof BoundPredicate); } Try / catch
try { return Spark3Util.describe(expr); } catch (UnsupportedOperationException e) { return expr.toString(); } Prevention
- Always keep the original unbound Expression for SQL rendering
- Never pass Binder.bind results to Spark3Util.describe
- Document that describe() expects unbound expressions only
When it happens
Trigger: Calling Spark3Util.describe (expressionsToSQL) with an Expression containing a predicate that has already been bound to a table schema (e.g. from SparkFilters.convert filters bound via Binder, or passing a result of binder.bind(expr)).
Common situations: Developers take a bound filter from a scan context and try to render it back to SQL for display/logging, hitting this throw; also when passing bound residual expressions from deletes/scan residual filtering.
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 predicate to SQL: %s
- Cannot convert term to SQL: %s
- Cannot convert type to SQL: %s
- Unsupported format in USING:
- Cannot convert bound predicates to SQL
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/aca6e88b5f526a26.
Report an issue: GitHub.