{"record":{"id":"8c408eb56be611ee","repo":"apache/druid","slug":"cluster-level-rules-cannot-be-empty","errorCode":null,"errorMessage":"Cluster-level rules cannot be empty.","messagePattern":"Cluster-level rules cannot be empty\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/metadata/SQLMetadataRuleManager.java","lineNumber":316,"sourceCode":"      } else {\n        log.error(e, \"Exception while polling for rules\");\n      }\n    }\n  }\n\n  @Override\n  public RetentionRulesSnapshot getRulesSnapshot()\n  {\n    return rulesSnapshot.get();\n  }\n\n  @Override\n  public boolean overrideRule(final String dataSource, final List<Rule> newRules, final AuditInfo auditInfo)\n  {\n    if (newRules == null) {\n      throw new IAE(\"Rules cannot be null.\");\n    } else if (newRules.isEmpty() && config.getDefaultRule().equals(dataSource)) {\n      throw new IAE(\"Cluster-level rules cannot be empty.\");\n    }\n\n    final String ruleString;\n    try {\n      ruleString = jsonMapper.writeValueAsString(newRules);\n      // uses getAllRules over getOverrideRules to allow proper audit trail when setting explicit [] override rules for\n      // a datasource with no existing overrides.\n      if (ruleString.equals(jsonMapper.writeValueAsString(rulesSnapshot.get().getAllRules().get(dataSource)))) {\n        log.info(\"Retention rules unchanged for datasource[%s] with rules[%s]\", dataSource, ruleString);\n        return true;\n      }\n      log.info(\"Updating datasource[%s] with rules[%s] as per [%s]\", dataSource, ruleString, auditInfo);\n    }\n    catch (JsonProcessingException e) {\n      log.error(e, \"Unable to write rules as string for datasource[%s]\", dataSource);\n      return false;\n    }\n    synchronized (lock) {","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/metadata/SQLMetadataRuleManager.java#L298-L334","documentation":"SQLMetadataRuleManager.overrideRule() throws IllegalArgumentException when an empty rule list is supplied for the special cluster-level (default) datasource. The cluster-level rule set is the fallback for all datasources and must contain at least one rule; clearing it would leave the cluster with no default load behavior.","triggerScenarios":"POSTing an empty rules array to the cluster-level default datasource rules endpoint (dataSource equal to config.getDefaultRule(), e.g. '_default'); programmatic overrideRule(\"_default\", Collections.emptyList(), auditInfo).","commonSituations":"Scripts that bulk-clear rules for 'all datasources' without excluding the default datasource; UI/tooling assuming empty rules are always acceptable.","solutions":["Provide at least one rule (e.g. a default loadForever rule) when overriding the cluster-level datasource.","Skip the default datasource in bulk-clear operations, or target specific datasources instead.","If the intent is to reset rules, replace with a sensible default rule set rather than an empty one."],"exampleFix":"// before\nruleManager.overrideRule(config.getDefaultRule(), Collections.emptyList(), auditInfo); // rejected\n// after\nruleManager.overrideRule(\n    config.getDefaultRule(),\n    Collections.singletonList(new PeriodLoadRule(Period.days(7), null, null, null, ImmutableMap.of())),\n    auditInfo);","handlingStrategy":"validation","validationCode":"if (dataSource.equals(config.getDefaultRule()) && rules.isEmpty()) {\n  throw new IllegalArgumentException(\"Cluster-level rules must contain at least one rule\");\n}","typeGuard":"boolean isLegalClusterOverride(String ds, List<Rule> rules) {\n  return !(config.getDefaultRule().equals(ds) && rules != null && rules.isEmpty());\n}","tryCatchPattern":"try {\n  ruleManager.overrideRule(dataSource, rules, auditInfo);\n} catch (IllegalArgumentException e) {\n  log.error(e, \"Cannot clear cluster-level rules: %s\", e.getMessage());\n}","preventionTips":["Exclude the default datasource from bulk rule-clearing scripts","Maintain a known-good default rule set for resets","Validate rule payloads against the datasource being targeted"],"tags":["rules","validation","rest-api"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}