{"record":{"id":"af49ed47fccb2d43","repo":"apache/druid","slug":"expected-key-indexspec-to-be-an-indexspec-but-g","errorCode":null,"errorMessage":"Expected key [indexSpec] to be an indexSpec, but got [%s]","messagePattern":"Expected key \\[indexSpec\\] to be an indexSpec, but got \\[(.+?)\\]","errorType":"exception","errorClass":"BadQueryContextException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java","lineNumber":754,"sourceCode":"\n  /**\n   * Decodes {@link #CTX_INDEX_SPEC} from either a JSON-encoded string, or POJOs.\n   */\n  @Nullable\n  @VisibleForTesting\n  static IndexSpec decodeIndexSpec(@Nullable final Object indexSpecObject, final ObjectMapper objectMapper)\n  {\n    try {\n      if (indexSpecObject == null) {\n        return null;\n      } else if (indexSpecObject instanceof String) {\n        return objectMapper.readValue((String) indexSpecObject, IndexSpec.class);\n      } else {\n        return objectMapper.convertValue(indexSpecObject, IndexSpec.class);\n      }\n    }\n    catch (Exception e) {\n      throw QueryContexts.badValueException(CTX_INDEX_SPEC, \"an indexSpec\", indexSpecObject);\n    }\n  }\n\n  /**\n   * This method is used to validate and get the taskLockType from the queryContext.\n   * If the queryContext does not contain the taskLockType, then {@link TaskLockType#EXCLUSIVE} is used for replace queries and\n   * {@link TaskLockType#SHARED} is used for insert queries.\n   * If the queryContext contains the taskLockType, then it is validated and returned.\n   */\n  public static TaskLockType validateAndGetTaskLockType(QueryContext queryContext, boolean isReplaceQuery)\n  {\n    final boolean useConcurrentLocks = queryContext.getBoolean(\n        Tasks.USE_CONCURRENT_LOCKS,\n        Tasks.DEFAULT_USE_CONCURRENT_LOCKS\n    );\n    if (useConcurrentLocks) {\n      return isReplaceQuery ? TaskLockType.REPLACE : TaskLockType.APPEND;\n    }","sourceCodeStart":736,"sourceCodeEnd":772,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/util/MultiStageQueryContext.java#L736-L772","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Validate the JSON against IndexSpec fields: only bitmap type, bitmapUncompressedable options, dimensionCompression, metricCompression, and long/double encoding keys are accepted.","Pass the indexSpec as a JSON string of a valid IndexSpec, e.g. '{\"dimensionCompression\":\"lz4\"}'.","Remove the indexSpec key to use defaults if custom compression is not required."],"exampleFix":"// before\nqueryContext.put(\"indexSpec\", \"{\\\"dimensionCompression\\\":\\\"snappy\\\"}\"); // snappy not supported\n// after\nqueryContext.put(\"indexSpec\", \"{\\\"dimensionCompression\\\":\\\"lz4\\\",\\\"metricCompression\\\":\\\"lz4\\\"}\");","handlingStrategy":"validation","validationCode":"Object idxSpec = queryContext.get(\"indexSpec\");\nif (idxSpec instanceof String) {\n  new ObjectMapper().readValue((String) idxSpec, IndexSpec.class); // throws early if invalid\n}","typeGuard":null,"tryCatchPattern":"try {\n  IndexSpec spec = MultiStageQueryContext.getIndexSpec(queryContext, mapper);\n} catch (QueryContextAwareException | BadJsonQueryException e) {\n  // inspect e.getMessage() for the offending indexSpec value\n}","preventionTips":["Only use IndexSpec-supported fields (dimensionCompression, metricCompression, bitmap type, encodings).","Copy indexSpec JSON from a known-good native ingestion spec validated on the same Druid version.","Omit the key to accept defaults when custom compression is unnecessary."],"tags":["druid","msq","query-context","indexspec","deserialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}