apache/druid · error · IllegalArgumentException

Rules cannot be null.

Error message

Rules cannot be null.

What it means

SQLMetadataRuleManager.overrideRule() persists an override of the load rules for a datasource. It throws IllegalArgumentException when the newRules list is null; an empty list is allowed (meaning 'no rules for this datasource, fall back to cluster defaults') but null is not a valid rule set.

Solutions

  1. Pass an empty list instead of null when you intend to clear datasource-specific rules.
  2. Fix the API client so the request body always includes a rules array.
  3. Add a null check on the rules list in the calling code before invoking overrideRule.

Example fix

// before
ruleManager.overrideRule(dataSource, newRules, auditInfo); // newRules may be null
// after
ruleManager.overrideRule(dataSource, newRules == null ? Collections.emptyList() : newRules, auditInfo);
Defensive patterns

Strategy: type-guard

Validate before calling

if (rules == null) rules = Collections.emptyList();

Type guard

List<Rule> safeRules(List<Rule> rules) { return rules == null ? Collections.emptyList() : rules; }

Try / catch

try {
  ruleManager.overrideRule(dataSource, rules, auditInfo);
} catch (IllegalArgumentException e) {
  log.error(e, "Rule override rejected: %s", e.getMessage());
}

Prevention

When it happens

Trigger: POSTing to the coordinator rules endpoint with a null 'rules' field; programmatic calls to overrideRule(dataSource, null, auditInfo); deserialization of a request body that omits the rules array.

Common situations: Automation scripts building rule payloads conditionally and omitting the rules key; API clients sending JSON without the rules property; migration tooling between rule APIs.

Related errors


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

Appendix: source

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

        log.makeAlert(e, "Exception while polling for rules").emit();
        failStartTimeMs = 0;
      } 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;

View on GitHub (pinned to 9b90983fd2)