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

  1. Do not call aggregate() twice on the same instance; reset() the aggregator between uses
  2. If seen in production, check for buffer-reuse bugs in the engine and report/fix the reset path
  3. 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

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


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;
  }

  @Override

View on GitHub (pinned to 9b90983fd2)