apache/druid · error · ISE
Can only handle [ ], got [ ]
Error message
Can only handle [%s], got [%s]
What it means
TopNQueryQueryToolChest only knows how to process TopNQuery objects. This guard in its query runner wiring throws IllegalStateException whenever a query of any other type is routed into the TopN tool chest, which is an internal dispatch invariant in Druid's query processing pipeline.
Solutions
- Ensure the Query passed to the TopN tool chest is constructed via TopNQueryBuilder / a TopNQueryDataSource
- Check QueryRunnerFactory registration so each query class maps to its own tool chest (QueryRunnerFactoryConglomerate)
- If writing custom dispatch code, instanceof-check the Query before invoking TopNQueryQueryToolChest
- Inspect serialized query payloads between broker and historical for type corruption
Example fix
// before
QueryRunner<Result<TopNResultValue>> runner = toolChest.getQueryRunner(seg, metric, new QueryPlus<>(genericQuery, null), ctx);
// after
if (genericQuery instanceof TopNQuery) {
QueryRunner<Result<TopNResultValue>> runner = toolChest.getQueryRunner(seg, metric, new QueryPlus<>((TopNQuery) genericQuery, null), ctx);
} else {
throw new IAE("Expected TopNQuery, got %s", genericQuery.getClass());
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!(query instanceof TopNQuery)) {
throw new IllegalArgumentException("TopN tool chest requires a TopNQuery, got " + query.getClass());
} Type guard
static boolean isTopNQuery(Query<?> q) {
return q instanceof TopNQuery;
} Try / catch
try {
runner.run(queryPlus, responseContext).toList();
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Can only handle")) {
throw new IllegalArgumentException("Wrong query type routed to TopN tool chest", e);
}
throw e;
} Prevention
- Build queries with TopNQueryBuilder so the concrete type is always TopNQuery
- Register each query class with its matching QueryRunnerFactory in the conglomerate
- instanceof-check query type before handing it to any QueryToolChest
When it happens
Trigger: Calling TopNQueryQueryToolChest.getQueryRunner (or the underlying runner) with a QueryPlus whose wrapped Query is not a TopNQuery — e.g. a misconfigured QueryRunnerFactoryRegistry maps a segment to the wrong tool chest, custom code passes a TimeseriesQuery/GroupByQuery/ScanQuery into the TopN tool chest directly, or a broker forwards a deserialized query whose type was corrupted.
Common situations: Custom extension code that builds a QueryRunner chain by hand and picks the wrong factory; bug in query-type dispatch after adding a new query type; tests that feed mock queries into a TopN tool chest.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Got a [ ] which isn't a
- AggregatorFactoryNotMergeableException
- Expected expression with just one binding
- Expected expression with just one binding
- Number of segment descriptors does not equal number of…
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/0dbcee331967115d.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/topn/TopNQueryQueryToolChest.java:623
{
private final QueryRunner<Result<TopNResultValue>> runner;
public ThresholdAdjustingQueryRunner(
QueryRunner<Result<TopNResultValue>> runner
)
{
this.runner = runner;
}
@Override
public Sequence<Result<TopNResultValue>> run(
QueryPlus<Result<TopNResultValue>> queryPlus,
ResponseContext responseContext
)
{
Query<Result<TopNResultValue>> input = queryPlus.getQuery();
if (!(input instanceof TopNQuery)) {
throw new ISE("Can only handle [%s], got [%s]", TopNQuery.class, input.getClass());
}
final TopNQuery query = (TopNQuery) input;
final int minTopNThreshold = query.context()
.getInt(QueryContexts.MIN_TOP_N_THRESHOLD, TopNQueryConfig.DEFAULT_MIN_TOPN_THRESHOLD);
if (query.getThreshold() > minTopNThreshold) {
return runner.run(queryPlus, responseContext);
}
final boolean isBySegment = query.context().isBySegment();
return Sequences.map(
runner.run(queryPlus.withQuery(query.withThreshold(minTopNThreshold)), responseContext),
new Function<>()
{
@Override
public Result<TopNResultValue> apply(Result<TopNResultValue> input)
{View on GitHub (pinned to 9b90983fd2)