apache/iceberg · error · IllegalStateException
Found already bound aggregate:
Error message
Found already bound aggregate:
What it means
Binder's aggregate method binds UnboundAggregate expressions; receiving an already-bound BoundAggregate means double binding, an internal caller bug. Binder throws this IllegalStateException immediately.
Source
Thrown at api/src/main/java/org/apache/iceberg/expressions/Binder.java:169
@Override
public <T> Expression predicate(BoundPredicate<T> pred) {
throw new IllegalStateException("Found already bound predicate: " + pred);
}
@Override
public <T> Expression predicate(UnboundPredicate<T> pred) {
return pred.bind(struct, caseSensitive);
}
@Override
public <T> Expression aggregate(UnboundAggregate<T> agg) {
return agg.bind(struct, caseSensitive);
}
@Override
public <T, C> Expression aggregate(BoundAggregate<T, C> agg) {
throw new IllegalStateException("Found already bound aggregate: " + agg);
}
}
private static class ReferenceVisitor extends ExpressionVisitor<Set<Integer>> {
private final Set<Integer> references = Sets.newHashSet();
@Override
public Set<Integer> alwaysTrue() {
return references;
}
@Override
public Set<Integer> alwaysFalse() {
return references;
}
@Override
public Set<Integer> not(Set<Integer> result) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Bind aggregate expressions once and store the BoundAggregate result for reuse
- Check Expression.isBound() (or use visitors) before binding
- Keep unbound template expressions immutable and produce fresh bound copies per use
Example fix
// before BoundAggregate<?, ?> agg = (BoundAggregate<?, ?>) Binder.bind(schema, unboundAgg); agg = (BoundAggregate<?, ?>) Binder.bind(schema, agg); // throws // after Expression agg = Binder.bind(schema, unboundAgg); // bind once, reuse
Defensive patterns
Strategy: type-guard
Validate before calling
if (expr.isBound()) { /* reuse as-is, do not re-bind */ } Type guard
boolean needsBinding = agg instanceof UnboundAggregate;
Try / catch
try { bound = Binder.bind(schema, aggExpr, caseSensitive); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Found already bound aggregate")) { bound = aggExpr; } else { throw e; } } Prevention
- Bind aggregate expressions once at planning time
- Guard binding with isBound() checks
- Store bound aggregates explicitly rather than re-binding cached templates
When it happens
Trigger: Passing an already-bound aggregate (e.g. from a prior bind() result) into ExpressionBinder.bind() again; binding aggregate expressions twice in scan planning pipelines.
Common situations: Reusing cached aggregate expressions across planning phases; custom scan code that binds aggregates for metrics evaluation and then re-binds the same objects.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Found already bound predicate:
- does not implement eval(StructLike)
- does not implement eval(DataFile)
- does not implement hasValue(DataFile)
- does not implement newAggregator()
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/65ae0c8b5826f9bd.
Report an issue: GitHub.