apache/druid · error · AggregatorFactoryNotMergeableException
AggregatorFactoryNotMergeableException
Error message
AggregatorFactoryNotMergeableException
What it means
Druid throws AggregatorFactoryNotMergeableException from JavaScriptAggregatorFactory.getMergingFactory when two aggregators from different query sub-results cannot be combined into a single merge step. For a merge to be allowed, both factories must have the same name and the same class (JavaScriptAggregatorFactory), and their fnCombine and fnReset JavaScript functions must be string-identical. If any of these checks fails, the engine cannot safely merge partial aggregation results, so it aborts with this exception.
Source
Thrown at processing/src/main/java/org/apache/druid/query/aggregation/JavaScriptAggregatorFactory.java:174
};
}
@Override
public AggregatorFactory getCombiningFactory()
{
return new JavaScriptAggregatorFactory(name, Collections.singletonList(name), fnCombine, fnReset, fnCombine, config);
}
@Override
public AggregatorFactory getMergingFactory(AggregatorFactory other) throws AggregatorFactoryNotMergeableException
{
if (other.getName().equals(this.getName()) && other.getClass() == this.getClass()) {
JavaScriptAggregatorFactory castedOther = (JavaScriptAggregatorFactory) other;
if (this.fnCombine.equals(castedOther.fnCombine) && this.fnReset.equals(castedOther.fnReset)) {
return getCombiningFactory();
}
}
throw new AggregatorFactoryNotMergeableException(this, other);
}
@Override
public Object deserialize(Object object)
{
// handle "NaN" / "Infinity" values serialized as strings in JSON
if (object instanceof String) {
return Double.parseDouble((String) object);
}
return object;
}
@Nullable
@Override
public Object finalizeComputation(@Nullable Object object)
{
return object;
}View on GitHub (pinned to 9b90983fd2)
Solutions
- Ensure every query layer (ingestion spec, query spec, sub-specs) defines the exact same fnCombine and fnReset JavaScript code for aggregators sharing a name.
- Verify all aggregators being merged at the same level are the same type and registered under the same unique name.
- Rewrite the aggregation to avoid merging incompatible specs (e.g. split into two queries and combine in application code, or use a post-aggregator instead).
- As a last resort, replace the JavaScript aggregator with a native Druid aggregator (e.g. doubleSum) whose merge semantics are uniform across specs.
Example fix
// before: two specs with same name, different combine scripts
// spec A: {"type":"javascript","name":"agg","fieldNames":["x"],"fnAggregate":...,"fnCombine":"function(a,b){return a+b;}","fnReset":"function(){return 0;}"}
// spec B: {"type":"javascript","name":"agg", ..., "fnCombine":"function(a,b){return Math.max(a,b);}"}
// after: identical fnCombine/fnReset in every spec that shares the aggregator name
// spec B: {"type":"javascript","name":"agg", ..., "fnCombine":"function(a,b){return a+b;}","fnReset":"function(){return 0;}"} Defensive patterns
Strategy: validation
Validate before calling
// before merging sub-aggregation specs, validate mergeability
static boolean mergeable(AggregatorFactory a, AggregatorFactory b) {
if (!(a instanceof JavaScriptAggregatorFactory) || !(b instanceof JavaScriptAggregatorFactory)) return false;
JavaScriptAggregatorFactory x = (JavaScriptAggregatorFactory) a, y = (JavaScriptAggregatorFactory) b;
return x.getName().equals(y.getName())
&& x.getFnCombine().equals(y.getFnCombine())
&& x.getFnReset().equals(y.getFnReset());
} Type guard
if (!(other instanceof JavaScriptAggregatorFactory)) {
// cannot merge; handle as incompatible aggregator type
} Try / catch
try {
AggregatorFactory merged = factory.getMergingFactory(other);
} catch (AggregatorFactoryNotMergeableException e) {
log.error(e, "Aggregators '%s' and '%s' cannot be merged; check name/type/fnCombine/fnReset", e.getThisFactory(), e.getOtherFactory());
// fail the query with a clear config error instead of retrying
} Prevention
- Keep JavaScript aggregator definitions (fnAggregate/fnCombine/fnReset) in one shared source of truth reused by every ingestion and query spec.
- Give each aggregator a unique name and never reuse a name for a different script or aggregator type.
- Review JS aggregator scripts in code review like regular code; changes to fnCombine must be rolled out to all specs simultaneously.
- Prefer native Druid aggregators over JavaScript ones so merge semantics are guaranteed by the engine.
When it happens
Trigger: Calling getMergingFactory(other) where (1) other's aggregator name differs from this factory's name, (2) other is a different AggregatorFactory subclass (e.g. LongSumAggregatorFactory paired with JavaScriptAggregatorFactory of the same name), or (3) both are JavaScriptAggregatorFactory with the same name but their fnCombine or fnReset scripts differ (string inequality). This fires during distributed query processing when the broker merges partial results from multiple segments or historical nodes.
Common situations: A datasource ingested with one JavaScript aggregator definition is queried with a modified script (fnCombine changed between versions); two aggregation specs in a multi-stage/merge query use the same output name but different JavaScript combine logic; a typo makes the aggregator name differ from the post-aggregation reference; mixing a native JavaScript aggregator with a SQL-generated equivalent aggregator on the same column.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- AggregatorFactoryNotMergeableException(this, other)
- [%s] does not implement makeAggregateCombiner()
- Unable to get SHA1 digest instance
- Emit called unexpectedly before service start
- Got an exception while parsing file [%s]
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/d15dbbbb59cc5f46.
Report an issue: GitHub.