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

  1. Ensure the Query passed to the TopN tool chest is constructed via TopNQueryBuilder / a TopNQueryDataSource
  2. Check QueryRunnerFactory registration so each query class maps to its own tool chest (QueryRunnerFactoryConglomerate)
  3. If writing custom dispatch code, instanceof-check the Query before invoking TopNQueryQueryToolChest
  4. 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

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


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)