{"record":{"id":"efdd8bdc0697395d","repo":"apache/druid","slug":"must-provide-at-least-one-range","errorCode":null,"errorMessage":"Must provide at least one range","messagePattern":"Must provide at least one range","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/key/ClusterByPartitions.java","lineNumber":49,"sourceCode":"/**\n * Holder object for a set of {@link ClusterByPartition}. There are no preconditions put upon the partitions, except\n * that there is at least one of them.\n *\n * In particular, they are not required to abut each other or to be non-overlapping. Use {@link #allAbutting()} to\n * check if this particular list of partitions is in fact all abutting (and, therefore, also non-overlapping).\n */\npublic class ClusterByPartitions implements Iterable<ClusterByPartition>\n{\n  private static final ClusterByPartitions ONE_UNIVERSAL_PARTITION =\n      new ClusterByPartitions(Collections.singletonList(new ClusterByPartition(null, null)));\n\n  private final List<ClusterByPartition> ranges;\n\n  @JsonCreator\n  public ClusterByPartitions(final List<ClusterByPartition> ranges)\n  {\n    if (ranges.isEmpty()) {\n      throw new IAE(\"Must provide at least one range\");\n    }\n\n    this.ranges = ranges;\n  }\n\n  public static ClusterByPartitions oneUniversalPartition()\n  {\n    return ONE_UNIVERSAL_PARTITION;\n  }\n\n  /**\n   * Whether this list of partitions is all abutting, meaning: each partition's start is equal to the previous\n   * partition's end.\n   *\n   * Note that the start of the first partition, and the end of the last partition, may or may not be unbounded.\n   * So this list of partitions may not cover the entire space even if they are all abutting.\n   */\n  public boolean allAbutting()","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/key/ClusterByPartitions.java#L31-L67","documentation":"ClusterByPartitions is a JSON-serialized list of ClusterByPartition ranges and must contain at least one range; an empty list is meaningless for partitioning. Its @JsonCreator constructor throws IAE when deserialized or constructed with an empty list.","triggerScenarios":"Deserializing a clustering/partitioning spec from JSON where the partitions array is [], or calling the constructor with Collections.emptyList() — e.g. a partitioning pass that produced no target ranges.","commonSituations":"MSQ query specs hand-edited or generated with an empty targetPartitions array; a partitioning job wrote an empty result that is then fed back as config; client code building specs dynamically from an empty set of partitions.","solutions":["Provide at least one ClusterByPartition range in the spec, or use ClusterByPartitions.oneUniversalPartition() when a single all-encompassing partition is desired.","Fix the upstream logic that generated the empty partitions list (it should fall back to a universal partition).","Validate the JSON spec before submission: partitions array must be non-empty.","If partitions are computed dynamically, guard: use oneUniversalPartition() when the computed list is empty."],"exampleFix":"// before\nnew ClusterByPartitions(Collections.emptyList()); // IAE\n// after\nList<ClusterByPartition> ranges = computePartitions();\nClusterByPartitions p = ranges.isEmpty()\n    ? ClusterByPartitions.oneUniversalPartition()\n    : new ClusterByPartitions(ranges);","handlingStrategy":"validation","validationCode":"if (ranges == null || ranges.isEmpty()) {\n  ranges = List.of(ClusterByPartition.universal()); // or oneUniversalPartition()\n}","typeGuard":"static boolean hasAtLeastOneRange(ClusterByPartitions p) {\n  return p != null && !p.ranges.isEmpty();\n}","tryCatchPattern":"try {\n  objectMapper.readValue(specJson, ClusterByPartitions.class);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"at least one range\")) {\n    return ClusterByPartitions.oneUniversalPartition();\n  }\n  throw e;\n}","preventionTips":["Use ClusterByPartitions.oneUniversalPartition() instead of an empty list when partitioning is effectively disabled.","Validate partitioning JSON specs for a non-empty targetPartitions array before submission.","Ensure partitioning passes emit a universal partition rather than nothing when they cannot split.","Add JSON schema checks on spec files that embed ClusterByPartitions."],"tags":["configuration","validation","json","druid","msq"],"backgroundTag":"empty-required-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"}