apache/druid · error · IllegalArgumentException
Expected [%d] children, got [%d]
Error message
Expected [%d] children, got [%d]
What it means
UnionDataSource.withChildren replaces each input datasource, so the children list must have exactly as many entries as the union's original dataSources. An arity mismatch would silently change the union's shape, so Druid throws IAE.
Source
Thrown at processing/src/main/java/org/apache/druid/query/UnionDataSource.java:98
*/
public boolean isTableBased()
{
return dataSources.stream()
.allMatch(dataSource -> dataSource instanceof TableDataSource
|| dataSource instanceof RestrictedDataSource);
}
@Override
public List<DataSource> getChildren()
{
return ImmutableList.copyOf(dataSources);
}
@Override
public DataSource withChildren(List<DataSource> children)
{
if (children.size() != dataSources.size()) {
throw new IAE("Expected [%d] children, got [%d]", dataSources.size(), children.size());
}
return new UnionDataSource(children);
}
@Override
public boolean isCacheable(boolean isBroker)
{
// Disables result-level caching for 'union' datasources, which doesn't work currently.
// See https://github.com/apache/druid/issues/8713 for reference.
//
// Note that per-segment caching is still effective, since at the time the per-segment cache evaluates a query
// for cacheability, it would have already been rewritten to a query on a single table.
return false;
}
@Override
public boolean isGlobal()View on GitHub (pinned to 9b90983fd2)
Solutions
- Pass children.size() == dataSources.size(), mapping each input to its replacement
- In rewrite code, keep a 1:1 correspondence between original inputs and replacements
- Use DataSourceAnalysis or children inspection to determine arity before calling
Example fix
// before DataSource rewritten = union.withChildren(collectAllRewrittenChildren(query)); // after List<DataSource> kids = union.getChildren().stream().map(this::rewrite).collect(Collectors.toList()); DataSource rewritten = union.withChildren(kids);
Defensive patterns
Strategy: validation
Validate before calling
if (children.size() != unionDs.getChildren().size()) {
throw new IllegalStateException("children must match union input count");
} Type guard
boolean arityMatches(UnionDataSource u, List<DataSource> children) {
return u.getChildren().size() == children.size();
} Try / catch
try {
DataSource rewritten = unionDs.withChildren(children);
} catch (IllegalArgumentException e) {
// rebuild children 1:1 from unionDs.getChildren()
} Prevention
- Always derive children from ds.getChildren() before replacing
- Never merge/drop branches inside a rewrite pass that later calls withChildren
When it happens
Trigger: Calling withChildren with a different number of datasources than the union contains — e.g. rewrite code that flattens or drops one branch before calling withChildren, or passing children of the whole query instead of this datasource.
Common situations: Planner/rewrite passes that rebuild datasource trees generically; code assuming all datasources have one child; unions composed of a dynamic number of tables.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Expected [1] child, got [%d]
- Expected [1] child, got [%d]
- Cannot handle dataSource [%s]
- Must have exactly one child
- Expected a TableDataSource, got data source type [%s]
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/d07dea6c339c4690.
Report an issue: GitHub.