apache/druid · error · UnsupportedOperationException

Must use 'filter' or 'always' havingSpec

Error message

Must use 'filter' or 'always' havingSpec

What it means

GroupByPostShuffleFrameProcessor.cloneHavingSpec can only clone HavingSpec types it supports (DimFilterHavingSpec and AlwaysHavingSpec), so the cloned having spec can be attached to the derived query. Any other HavingSpec implementation triggers this UnsupportedOperationException because the processor cannot rewrite it correctly for the post-shuffle query.

Solutions

  1. Replace the having spec with a DimFilterHavingSpec (a filter-based HAVING clause, e.g. HAVING SUM(x) > 10 in SQL).
  2. In SQL, express the HAVING condition as a standard filter so it compiles to DimFilterHavingSpec.
  3. Move the having logic into an outer query (subquery aggregation, then filter outside).
  4. Remove the havingSpec and filter results client-side if unsupported.

Example fix

// before (native groupBy)
"havingSpec": {"type": "greaterThan", "aggregation": "agg", "value": 10}
// after
"havingSpec": {"type": "filter", "dimFilter": {"type": "expression", "expression": "\"agg\" > 10"}}
Defensive patterns

Strategy: validation

Validate before calling

// Check the havingSpec type before submitting a group-by through MSQ
if (q.havingSpec && !['filter', 'always'].includes(q.havingSpec.type)) {
  throw new Error('MSQ group-by supports only filter/always havingSpec; got: ' + q.havingSpec.type);
}

Type guard

const hasSupportedHaving = (q) => !q.havingSpec || q.havingSpec.type === 'filter' || q.havingSpec.type === 'always';

Prevention

When it happens

Trigger: Running a group-by query through MSQ post-shuffle processing where the query's havingSpec is neither a DimFilterHavingSpec nor an AlwaysHavingSpec (e.g. a legacy ComparatorHavingSpec, or a custom HavingSpec).

Common situations: Older/native queries using comparator-based having specs (e.g. 'greaterThan' numeric having) submitted through SQL/MSQ; custom HavingSpec plugins from extensions; queries built by older tooling.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/querykit/groupby/GroupByPostShuffleFrameProcessor.java:343

      return row -> {};
    }
  }

  @Nullable
  private static HavingSpec cloneHavingSpec(final GroupByQuery query)
  {
    if (query.getHavingSpec() == null || query.getHavingSpec() instanceof AlwaysHavingSpec) {
      return null;
    } else if (query.getHavingSpec() instanceof DimFilterHavingSpec) {
      final DimFilterHavingSpec dimFilterHavingSpec = (DimFilterHavingSpec) query.getHavingSpec();
      final DimFilterHavingSpec clonedHavingSpec = new DimFilterHavingSpec(
          dimFilterHavingSpec.getDimFilter(),
          dimFilterHavingSpec.isFinalize()
      );
      clonedHavingSpec.setQuery(query);
      return clonedHavingSpec;
    } else {
      throw new UnsupportedOperationException("Must use 'filter' or 'always' havingSpec");
    }
  }

  /**
   * Create virtual columns containing "bonus" fields that should be attached to the {@link FrameWriter} for
   * this processor. Kept in sync with the signature generated by {@link GroupByQueryKit}.
   */
  private static VirtualColumns makeVirtualColumnsForFrameWriter(
      @Nullable final VirtualColumn partitionBoostVirtualColumn,
      final ObjectMapper jsonMapper,
      final GroupByQuery query
  )
  {
    List<VirtualColumn> virtualColumns = new ArrayList<>();

    virtualColumns.add(partitionBoostVirtualColumn);
    final VirtualColumn segmentGranularityVirtualColumn =
        QueryKitUtils.makeSegmentGranularityVirtualColumn(jsonMapper, query.context());

View on GitHub (pinned to 9b90983fd2)