apache/druid · error · IllegalStateException

Ambiguous build, limitSpec

Error message

Ambiguous build, limitSpec[%s] already set

What it means

GroupByQuery.Builder supports two ways to specify limits: setLimitSpec(...) and the fluent setLimit()/addOrderByColumnSpec() API. Using both is ambiguous, so ensureExplicitLimitSpecNotSet throws ISE when an explicit limitSpec is already set and fluent limit methods are then used.

Solutions

  1. Pick one API: use only setLimitSpec OR only the fluent setLimit/addOrderByColumnSpec methods
  2. Before fluent calls, guard with hasFluentization-like checks or clear the existing limitSpec
  3. Remove the redundant setLimitSpec call when fluent limits already encode the desired limit/sort

Example fix

// before
builder.setLimitSpec(limitSpec).setLimit(10);
// after
builder.setLimitSpec(limitSpec); // or: builder.setLimit(10).addOrderByColumnSpec(...);
Defensive patterns

Strategy: validation

Validate before calling

if (builder != null && explicitLimitSpecSet && fluentLimitUsed) throw new IllegalStateException("Use either setLimitSpec or fluent limit APIs, not both");

Type guard

null

Try / catch

try { builder.build(); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Ambiguous build")) { /* choose one limit mechanism and rebuild */ } throw e; }

Prevention

When it happens

Trigger: Calling Builder.setLimitSpec(...) (or limitSpec field set) and subsequently calling setLimit/addOrderByColumnSpec/limit-fluent methods on the same builder.

Common situations: Query-builder code mixing legacy limitSpec JSON with newer fluent API calls; deserialization followed by programmatic limit modification; refactor leftovers.

Related errors


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

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/query/groupby/GroupByQuery.java:1125

      ensureExplicitLimitSpecNotSet();
      this.orderByColumnSpecs = new ArrayList<>(columnSpec);
      this.postProcessingFn = null;
      return this;
    }

    public Builder setLimitSpec(LimitSpec limitSpec)
    {
      Preconditions.checkNotNull(limitSpec);
      ensureFluentLimitsNotSet();
      this.limitSpec = limitSpec;
      this.postProcessingFn = null;
      return this;
    }

    private void ensureExplicitLimitSpecNotSet()
    {
      if (limitSpec != null) {
        throw new ISE("Ambiguous build, limitSpec[%s] already set", limitSpec);
      }
    }

    private void ensureFluentLimitsNotSet()
    {
      if (!(limit == Integer.MAX_VALUE && orderByColumnSpecs.isEmpty())) {
        throw new ISE("Ambiguous build, limit[%s] or columnSpecs[%s] already set.", limit, orderByColumnSpecs);
      }
    }

    public Builder setQuerySegmentSpec(QuerySegmentSpec querySegmentSpec)
    {
      this.querySegmentSpec = querySegmentSpec;
      return this;
    }

    public Builder setDimFilter(@Nullable DimFilter dimFilter)
    {

View on GitHub (pinned to 9b90983fd2)