apache/druid · error · IllegalStateException
Cannot set twice
Error message
Cannot set twice
What it means
PassthroughAggregator is a single-use aggregator that stores exactly one object from its selector. aggregate() enforces the aggregator lifecycle contract (one aggregation per row slot); calling aggregate() twice without resetting violates it and throws ISE.
Solutions
- Do not call aggregate() twice on the same instance; reset() the aggregator between uses
- If seen in production, check for buffer-reuse bugs in the engine and report/fix the reset path
- In tests, call reset() before each aggregate()
Example fix
// before agg.aggregate(); agg.aggregate(); // ISE // after agg.aggregate(); agg.reset(); agg.aggregate();
Defensive patterns
Strategy: try-catch
Try / catch
try {
aggregator.aggregate();
} catch (ISE e) {
if ("Cannot set twice".equals(e.getMessage())) { aggregator.reset(); aggregator.aggregate(); }
else throw e;
} Prevention
- Call reset() before reusing an aggregator instance
- Never manually drive aggregate() twice in tests without reset
- Treat aggregator instances as single-use per row/group
When it happens
Trigger: The query engine invokes aggregate() more than once on the same aggregator instance, typically due to a custom aggregator/engine misuse or a bug in buffer reuse rather than user SQL.
Common situations: Custom query engine or extension reusing aggregator buffers incorrectly; testing harness calling aggregate() manually twice.
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
- Cannot call get() before aggregate()
- Already closed
- Already closed
- Already shut down, not starting again
- Already started.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/424b662b6a245f16.
Report an issue: GitHub.
Appendix: source
Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/util/PassthroughAggregator.java:43
import javax.annotation.Nullable;
public class PassthroughAggregator implements Aggregator
{
private final BaseObjectColumnValueSelector<?> selector;
private boolean didSet = false;
private Object val;
public PassthroughAggregator(final BaseObjectColumnValueSelector<?> selector)
{
this.selector = selector;
}
@Override
public void aggregate()
{
if (didSet) {
throw new ISE("Cannot set twice");
}
val = selector.getObject();
didSet = true;
}
@Nullable
@Override
public Object get()
{
if (!didSet) {
throw new ISE("Cannot call get() before aggregate()");
}
return val;
}
@OverrideView on GitHub (pinned to 9b90983fd2)