apache/iceberg · error · UnsupportedOperationException
Cannot convert non-reference to Parquet filter
Error message
Cannot convert non-reference to Parquet filter: <term>
What it means
The Parquet filter translator converts bound Iceberg predicates into parquet-mr FilterPredicates for pushdown. Parquet can only evaluate predicates on direct column references; if a bound predicate's term is not a BoundReference (e.g. a BoundTransform or other composite term), it cannot be pushed down and this UnsupportedOperationException is thrown.
Solutions
- Rewrite the filter to reference the raw column and apply transforms after reading, or pre-filter unsupported predicates at planning time.
- Only pass predicates whose terms are plain references to predicate()/ParquetFilters.
- Upgrade Iceberg — newer code paths remove unsupported predicates instead of throwing.
Example fix
// before
filter = Expressions.equal(Expressions.bucket("id", 16), 5); // pushed straight down
// after
filter = Expressions.in("id", <resolved bucket member values>); // push on the reference, transform applied at planning time Defensive patterns
Strategy: validation
Validate before calling
// only push down predicates on plain column references
if (pred instanceof BoundPredicate
&& !(((BoundPredicate<?>) pred).term() instanceof BoundReference)) {
// do not push this predicate down; keep it for post-scan evaluation
} Type guard
boolean isPushable(BoundPredicate<?> pred) {
return pred.term() instanceof BoundReference;
} Try / catch
try {
FilterPredicate fp = parquetFilters.predicate(pred);
} catch (UnsupportedOperationException e) {
LOG.debug("Predicate not pushable: {}", e.getMessage());
unpushed.add(pred);
} Prevention
- Apply transforms (bucket/truncate/year) at planning time, not inside pushed-down terms.
- Check pred.term() instanceof BoundReference before attempting pushdown.
- Keep engine expression-binding code in sync with the Parquet translator's capabilities.
When it happens
Trigger: Evaluating an expression containing transform terms (e.g. bucket(...), truncate(...), year(ts)) against Parquet with this translator when the term binds to something other than a plain column reference.
Common situations: Queries with partition-transform expressions in filters combined with strict pushdown paths; custom expressions using BoundTransform; engine integration pushing whole bound expression trees into ParquetFilters.
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 to Parquet filter
- Failed to check if can be pushed down
- Failed to read required dictionary page for id
- AlwaysFalse is a placeholder only
- AlwaysTrue is a placeholder only
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/363afb672efc92ab.
Report an issue: GitHub.
Appendix: source
Thrown at parquet/src/main/java/org/apache/iceberg/parquet/ParquetFilters.java:114
public FilterPredicate or(FilterPredicate left, FilterPredicate right) {
if (left == AlwaysTrue.INSTANCE || right == AlwaysTrue.INSTANCE) {
return AlwaysTrue.INSTANCE;
} else if (left == AlwaysFalse.INSTANCE) {
return right;
} else if (right == AlwaysFalse.INSTANCE) {
return left;
}
return FilterApi.or(left, right);
}
protected Expression bind(UnboundPredicate<?> pred) {
return pred.bind(schema.asStruct(), caseSensitive);
}
@Override
public <T> FilterPredicate predicate(BoundPredicate<T> pred) {
if (!(pred.term() instanceof BoundReference)) {
throw new UnsupportedOperationException(
"Cannot convert non-reference to Parquet filter: " + pred.term());
}
Operation op = pred.op();
BoundReference<T> ref = (BoundReference<T>) pred.term();
String path = schema.idToAlias(ref.fieldId());
Literal<T> lit;
if (pred.isUnaryPredicate()) {
lit = null;
} else if (pred.isLiteralPredicate()) {
lit = pred.asLiteralPredicate().literal();
} else {
throw new UnsupportedOperationException("Cannot convert to Parquet filter: " + pred);
}
switch (ref.type().typeId()) {
case BOOLEAN:
Operators.BooleanColumn col = FilterApi.booleanColumn(path);View on GitHub (pinned to 86d9c8fc54)