apache/druid · error · IllegalArgumentException

Cluster-level rules cannot be empty.

Error message

Cluster-level rules cannot be empty.

What it means

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.

Solutions

  1. Provide at least one rule (e.g. a default loadForever rule) when overriding the cluster-level datasource.
  2. Skip the default datasource in bulk-clear operations, or target specific datasources instead.
  3. If the intent is to reset rules, replace with a sensible default rule set rather than an empty one.

Example fix

// before
ruleManager.overrideRule(config.getDefaultRule(), Collections.emptyList(), auditInfo); // rejected
// after
ruleManager.overrideRule(
    config.getDefaultRule(),
    Collections.singletonList(new PeriodLoadRule(Period.days(7), null, null, null, ImmutableMap.of())),
    auditInfo);
Defensive patterns

Strategy: validation

Validate before calling

if (dataSource.equals(config.getDefaultRule()) && rules.isEmpty()) {
  throw new IllegalArgumentException("Cluster-level rules must contain at least one rule");
}

Type guard

boolean isLegalClusterOverride(String ds, List<Rule> rules) {
  return !(config.getDefaultRule().equals(ds) && rules != null && rules.isEmpty());
}

Try / catch

try {
  ruleManager.overrideRule(dataSource, rules, auditInfo);
} catch (IllegalArgumentException e) {
  log.error(e, "Cannot clear cluster-level rules: %s", e.getMessage());
}

Prevention

When it happens

Trigger: 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).

Common situations: Scripts that bulk-clear rules for 'all datasources' without excluding the default datasource; UI/tooling assuming empty rules are always acceptable.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at server/src/main/java/org/apache/druid/metadata/SQLMetadataRuleManager.java:316

      } else {
        log.error(e, "Exception while polling for rules");
      }
    }
  }

  @Override
  public RetentionRulesSnapshot getRulesSnapshot()
  {
    return rulesSnapshot.get();
  }

  @Override
  public boolean overrideRule(final String dataSource, final List<Rule> newRules, final AuditInfo auditInfo)
  {
    if (newRules == null) {
      throw new IAE("Rules cannot be null.");
    } else if (newRules.isEmpty() && config.getDefaultRule().equals(dataSource)) {
      throw new IAE("Cluster-level rules cannot be empty.");
    }

    final String ruleString;
    try {
      ruleString = jsonMapper.writeValueAsString(newRules);
      // uses getAllRules over getOverrideRules to allow proper audit trail when setting explicit [] override rules for
      // a datasource with no existing overrides.
      if (ruleString.equals(jsonMapper.writeValueAsString(rulesSnapshot.get().getAllRules().get(dataSource)))) {
        log.info("Retention rules unchanged for datasource[%s] with rules[%s]", dataSource, ruleString);
        return true;
      }
      log.info("Updating datasource[%s] with rules[%s] as per [%s]", dataSource, ruleString, auditInfo);
    }
    catch (JsonProcessingException e) {
      log.error(e, "Unable to write rules as string for datasource[%s]", dataSource);
      return false;
    }
    synchronized (lock) {

View on GitHub (pinned to 9b90983fd2)