apache/druid · error · BadQueryContextException

Expected key [indexSpec] to be an indexSpec, but got [%s]

Error message

Expected key [indexSpec] to be an indexSpec, but got [%s]

What it means

MultiStageQueryContext.getIndexSpec decodes the 'indexSpec' query context key into an org.apache.druid.segment.indexing.IndexSpec, either from a JSON string or by converting a POJO/map. If both deserialization approaches fail, it throws badValueException stating the key must be 'an indexSpec'. The query is rejected at planning time.

Source

Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java:754

  /**
   * Decodes {@link #CTX_INDEX_SPEC} from either a JSON-encoded string, or POJOs.
   */
  @Nullable
  @VisibleForTesting
  static IndexSpec decodeIndexSpec(@Nullable final Object indexSpecObject, final ObjectMapper objectMapper)
  {
    try {
      if (indexSpecObject == null) {
        return null;
      } else if (indexSpecObject instanceof String) {
        return objectMapper.readValue((String) indexSpecObject, IndexSpec.class);
      } else {
        return objectMapper.convertValue(indexSpecObject, IndexSpec.class);
      }
    }
    catch (Exception e) {
      throw QueryContexts.badValueException(CTX_INDEX_SPEC, "an indexSpec", indexSpecObject);
    }
  }

  /**
   * This method is used to validate and get the taskLockType from the queryContext.
   * If the queryContext does not contain the taskLockType, then {@link TaskLockType#EXCLUSIVE} is used for replace queries and
   * {@link TaskLockType#SHARED} is used for insert queries.
   * If the queryContext contains the taskLockType, then it is validated and returned.
   */
  public static TaskLockType validateAndGetTaskLockType(QueryContext queryContext, boolean isReplaceQuery)
  {
    final boolean useConcurrentLocks = queryContext.getBoolean(
        Tasks.USE_CONCURRENT_LOCKS,
        Tasks.DEFAULT_USE_CONCURRENT_LOCKS
    );
    if (useConcurrentLocks) {
      return isReplaceQuery ? TaskLockType.REPLACE : TaskLockType.APPEND;
    }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Validate the JSON against IndexSpec fields: only bitmap type, bitmapUncompressedable options, dimensionCompression, metricCompression, and long/double encoding keys are accepted.
  2. Pass the indexSpec as a JSON string of a valid IndexSpec, e.g. '{"dimensionCompression":"lz4"}'.
  3. Remove the indexSpec key to use defaults if custom compression is not required.

Example fix

// before
queryContext.put("indexSpec", "{\"dimensionCompression\":\"snappy\"}"); // snappy not supported
// after
queryContext.put("indexSpec", "{\"dimensionCompression\":\"lz4\",\"metricCompression\":\"lz4\"}");
Defensive patterns

Strategy: validation

Validate before calling

Object idxSpec = queryContext.get("indexSpec");
if (idxSpec instanceof String) {
  new ObjectMapper().readValue((String) idxSpec, IndexSpec.class); // throws early if invalid
}

Try / catch

try {
  IndexSpec spec = MultiStageQueryContext.getIndexSpec(queryContext, mapper);
} catch (QueryContextAwareException | BadJsonQueryException e) {
  // inspect e.getMessage() for the offending indexSpec value
}

Prevention

When it happens

Trigger: Setting query context key 'indexSpec' to a value that does not deserialize to IndexSpec: an unknown property name, a wrong-typed field (e.g. dimensionCompression: 5), or a JSON string of a different object type.

Common situations: Users copy an ingestion-spec indexSpec snippet into the query context with incompatible fields (e.g. rollup-specific keys), misspell properties like 'dimensionCompression', or pass a map with nested objects that do not match IndexSpec's schema.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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