{"record":{"id":"1a112f3f1ca68fd1","repo":"apache/druid","slug":"partitiondimensions-must-be-specified","errorCode":null,"errorMessage":"partitionDimensions must be specified","messagePattern":"partitionDimensions must be specified","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexTuningConfig.java","lineNumber":218,"sourceCode":"    this.maxNumSegmentsToMerge = maxNumSegmentsToMerge == null\n                                 ? DEFAULT_MAX_NUM_SEGMENTS_TO_MERGE\n                                 : maxNumSegmentsToMerge;\n\n    this.totalNumMergeTasks = totalNumMergeTasks == null\n                            ? DEFAULT_TOTAL_NUM_MERGE_TASKS\n                            : totalNumMergeTasks;\n\n    this.maxAllowedLockCount = maxAllowedLockCount == null\n                               ? DEFAULT_MAX_ALLOWED_LOCK_COUNT\n                               : maxAllowedLockCount;\n\n    Preconditions.checkArgument(this.maxNumConcurrentSubTasks > 0, \"maxNumConcurrentSubTasks must be positive\");\n    Preconditions.checkArgument(this.maxNumSegmentsToMerge > 0, \"maxNumSegmentsToMerge must be positive\");\n    Preconditions.checkArgument(this.totalNumMergeTasks > 0, \"totalNumMergeTasks must be positive\");\n    if (getPartitionsSpec() != null && getPartitionsSpec() instanceof DimensionRangePartitionsSpec) {\n      List<String> partitionDimensions = ((DimensionRangePartitionsSpec) getPartitionsSpec()).getPartitionDimensions();\n      if (partitionDimensions == null || partitionDimensions.isEmpty()) {\n        throw new IAE(\"partitionDimensions must be specified\");\n      }\n    }\n  }\n\n  @Nullable\n  @JsonProperty\n  public SplitHintSpec getSplitHintSpec()\n  {\n    return splitHintSpec;\n  }\n\n  @JsonProperty\n  public int getMaxNumConcurrentSubTasks()\n  {\n    return maxNumConcurrentSubTasks;\n  }\n\n  @JsonProperty","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/indexing-service/src/main/java/org/apache/druid/indexing/common/task/batch/parallel/ParallelIndexTuningConfig.java#L200-L236","documentation":"Parallel (native batch) index tasks require that when a range partitioning strategy is used with dynamic partitioning disabled (DimensionRangePartitionsSpec), the list of partition dimensions must be explicitly provided. The tuning config validation throws IAE when a DimensionRangePartitionsSpec is present but its partitionDimensions field is null or empty, because range partitioning needs at least one dimension to compute ranges on.","triggerScenarios":"Submitting a ParallelIndexTuningConfig (e.g. via a batch ingestion spec) whose partitionsSpec has type 'range' but omits partitionDimensions, or sends an empty list for it.","commonSituations":"Users migrating from 'hashed' or 'dynamic' partitioning to 'range' partitioning and forgetting that range partitioning in Druid requires partitionDimensions; hand-edited JSON specs where the field was deleted; programmatically built specs that set only maxRowsPerSegment/targetRowsPerSegment.","solutions":["Add a non-empty partitionDimensions array to the 'partitionsSpec' in the tuning config (e.g. \"partitionDimensions\": [\"dim1\"]).","If you don't need range partitioning, switch partitionsSpec type to 'hashed' or 'dynamic', which don't require partitionDimensions.","Validate the spec JSON with Druid's ingestion spec validator / MSQ explainer before submitting."],"exampleFix":"// before\n\"partitionsSpec\": {\n  \"type\": \"range\",\n  \"maxRowsPerSegment\": 5000000\n}\n// after\n\"partitionsSpec\": {\n  \"type\": \"range\",\n  \"partitionDimensions\": [\"channel\"],\n  \"maxRowsPerSegment\": 5000000\n}","handlingStrategy":"validation","validationCode":"// before submitting the tuning config\nif (\"range\".equals(partitionsSpec.get(\"type\"))\n    && (partitionsSpec.get(\"partitionDimensions\") == null\n        || ((List<?>) partitionsSpec.get(\"partitionDimensions\")).isEmpty())) {\n  throw new IllegalArgumentException(\"range partitionsSpec requires non-empty partitionDimensions\");\n}","typeGuard":null,"tryCatchPattern":"try { submitSpec(spec); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"partitionDimensions must be specified\")) { /* fix spec: add partitionDimensions */ } else { throw e; } }","preventionTips":["Always include partitionDimensions when partitionsSpec type is 'range'.","Validate ingestion specs against Druid's JSON schema before submit.","When migrating from hashed/dynamic to range partitioning, audit all tuning-config fields."],"tags":["druid","batch-ingestion","partitioning","config-validation"],"backgroundTag":"missing-required-config-field","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"}