apache/druid · error · UnsupportedOperationException

Cannot change output name for AggregatorFactory

Error message

Cannot change output name for AggregatorFactory[%s].

What it means

AggregatorFactory.withName(newName) is a no-op hook in the base class that throws UOE; only aggregator factories that support renaming override it. Calling it on an unsupported factory means the requested output-name substitution cannot be performed.

Solutions

  1. Override withName(String) in your AggregatorFactory subclass to return a copy with the new output name
  2. Use an aggregator implementation that supports withName
  3. Restructure the query to avoid renaming this aggregator (e.g. alias at a different layer)

Example fix

// before
// base class behavior
throw new UOE("Cannot change output name for AggregatorFactory[%s].", getClass().getName());
// after
@Override
public AggregatorFactory withName(String newName) {
  return new DoubleSumAggregatorFactory(newName, fieldName, expression);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before renaming
Method m = factory.getClass().getMethod("withName", String.class);
if (m.getDeclaringClass().equals(AggregatorFactory.class)) {
  throw new IllegalStateException("Factory does not support withName: " + factory.getClass());
}

Type guard

boolean supportsRename(AggregatorFactory f) {
  try { f.withName("probe"); return true; } catch (UnsupportedOperationException e) { return false; }
}

Try / catch

try {
  renamed = factory.withName(newName);
} catch (UnsupportedOperationException e) {
  log.error("withName unsupported for %s", factory.getClass(), e);
  throw e;
}

Prevention

When it happens

Trigger: Calling withName() on a concrete AggregatorFactory that does not override it; callers include testWithName, substituteCombiningFactory, and getCombiningFactory paths during query planning/rewriting.

Common situations: Custom aggregators lacking a withName override used in queries that require renaming (e.g. nested groupBy or post-aggregation rewriting); older aggregator implementations not updated for the renaming API.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/adb019c028f74f29. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/aggregation/AggregatorFactory.java:367

  {
    return this;
  }

  /**
   * Used in cases where we want to change the output name of the aggregator to something else. For eg: if we have
   * a query `select a, sum(b) as total group by a from table` the aggregator returned from the native group by query is "a0" set in
   * {@link org.apache.druid.sql.calcite.rel.DruidQuery#computeAggregations}. We can use withName("total") to set the output name
   * of the aggregator to "total".
   * <p>
   * As all implementations of this interface method may not exist, callers of this method are advised to handle such a case.
   *
   * @param newName newName of the output for aggregator factory
   * @return AggregatorFactory with the output name set as the input param.
   */
  @SuppressWarnings("unused")
  public AggregatorFactory withName(String newName)
  {
    throw new UOE("Cannot change output name for AggregatorFactory[%s].", this.getClass().getName());
  }

  /**
   * Check to see if we can make a 'combining' factory of this aggregator that is suitable to process input from a
   * selector of values produced by the other {@link AggregatorFactory} representing pre-aggregated data. Typically,
   * this means that this and the other aggregator have the same inputs ({@link #requiredFields()}, and the same
   * options for how the data was constructed into the intermediary type. If suitable, this method returns a
   * 'combining' aggregator factory of this aggregator to use to process the pre-aggregated data which was produced by
   * the other aggregator.
   * <p>
   * This method is used indirectly in service of checking if a
   * {@link org.apache.druid.segment.projections.QueryableProjection} can be used instead of the base table during
   * {@link org.apache.druid.segment.CursorFactory#makeCursorHolder(CursorBuildSpec)}, which checks if this
   * aggregator can be substituted for its combining aggregator if and only if there exists a column that a cursor can
   * read which was created by an aggregator that satisfies this method. In other words, this aggregator is the 'query'
   * aggregator defined on the {@link CursorBuildSpec}, the argument to this method is the aggregator which created
   * some column whose selectors are available to the cursor. If all aggregators on the {@link CursorBuildSpec} can be
   * paired with aggregators from the underlying table in the cursor factory, then

View on GitHub (pinned to 9b90983fd2)