apache/druid · error · IllegalArgumentException

Granularity [%s] is not supported

Error message

Granularity [%s] is not supported

What it means

Thrown by QueryKitUtils.makeSegmentGranularityVirtualColumn when the configured segment granularity is not Granularities.ALL and not a PeriodGranularity. MSQ builds a time-flooring virtual column from a PeriodGranularity; other Granularity implementations (or period granularities with a non-UTC time zone / custom origin) cannot be turned into the bucketing column.

Source

Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/querykit/QueryKitUtils.java:214

   *
   * @throws IllegalArgumentException if the provided granularity is not supported
   */
  @Nullable
  public static VirtualColumn makeSegmentGranularityVirtualColumn(
      final ObjectMapper jsonMapper,
      final QueryContext queryContext
  )
  {
    final Granularity segmentGranularity =
        QueryKitUtils.getSegmentGranularityFromContext(jsonMapper, queryContext.asMap());
    final String timeColumnName = queryContext.getString(QueryKitUtils.CTX_TIME_COLUMN_NAME);

    if (timeColumnName == null || Granularities.ALL.equals(segmentGranularity)) {
      return null;
    }

    if (!(segmentGranularity instanceof PeriodGranularity)) {
      throw new IAE("Granularity [%s] is not supported", segmentGranularity);
    }

    final PeriodGranularity periodSegmentGranularity = (PeriodGranularity) segmentGranularity;

    if (periodSegmentGranularity.getOrigin() != null
        || !periodSegmentGranularity.getTimeZone().equals(DateTimeZone.UTC)) {
      throw new IAE("Granularity [%s] is not supported", segmentGranularity);
    }

    return new ExpressionVirtualColumn(
        QueryKitUtils.SEGMENT_GRANULARITY_COLUMN,
        StringUtils.format(
            "timestamp_floor(%s, %s)",
            CalciteSqlDialect.DEFAULT.quoteIdentifier(timeColumnName),
            Calcites.escapeStringLiteral((periodSegmentGranularity).getPeriod().toString())
        ),
        ColumnType.LONG,
        new ExprMacroTable(Collections.singletonList(new TimestampFloorExprMacro()))

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Use a plain period granularity in UTC with no origin, e.g. {"type":"period","period":"P1D"} (equivalent to "day").
  2. Use a standard named granularity such as "hour", "day", "month".
  3. If local-timezone bucketing is needed, precompute the bucket in SQL (e.g. TIME_FLOOR with TIME_SHIFT) instead of relying on segment granularity.
  4. Remove a custom origin from the period granularity or align data upstream.

Example fix

// before
context.put("segmentGranularity", new DurationGranularity(86400000L, 0L));
// after
context.put("segmentGranularity", new PeriodGranularity(Period.days(1), null, DateTimeZone.UTC));
Defensive patterns

Strategy: validation

Validate before calling

Granularity g = segmentGranularity;
if (g != null && !Granularities.ALL.equals(g) && !(g instanceof PeriodGranularity)) {
  throw new IllegalArgumentException("Segment granularity must be a UTC period granularity");
}
if (g instanceof PeriodGranularity) {
  PeriodGranularity pg = (PeriodGranularity) g;
  if (pg.getOrigin() != null || !pg.getTimeZone().equals(DateTimeZone.UTC)) {
    throw new IllegalArgumentException("Period granularity must have null origin and UTC timezone");
  }
}

Type guard

boolean isSupportedSegmentGranularity(Object g) {
  return g == null || Granularities.ALL.equals(g)
      || (g instanceof PeriodGranularity
          && ((PeriodGranularity) g).getOrigin() == null
          && DateTimeZone.UTC.equals(((PeriodGranularity) g).getTimeZone()));
}

Try / catch

try {
  VirtualColumn vc = QueryKitUtils.makeSegmentGranularityVirtualColumn(timeCol, granularity);
} catch (IllegalArgumentException e) {
  granularity = Granularities.DAY; // fall back to a supported period granularity
}

Prevention

When it happens

Trigger: Setting the segment granularity context to a granularity type other than PeriodGranularity (e.g. DurationGranularity or a custom implementation), or a PeriodGranularity with an explicit origin or non-UTC timeZone, then generating the __granularity virtual column.

Common situations: Custom granularity objects supplied programmatically; period granularities with timezone offsets for local-time bucketing (unsupported by this path); configs migrated from ingestion specs using duration-based granularities.

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


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