flowable/flowable-engine · error · ActivitiIllegalArgumentException

Candidate group is null

Error message

Candidate group is null

What it means

HistoricTaskInstanceQueryImpl.taskCandidateGroup(String) rejects a null candidateGroup with ActivitiIllegalArgumentException. Like the other setters here it validates eagerly before mutating query state.

Source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/HistoricTaskInstanceQueryImpl.java:1081

    @Override
    public HistoricTaskInstanceQuery taskCandidateUser(String candidateUser) {
        if (candidateUser == null) {
            throw new ActivitiIllegalArgumentException("Candidate user is null");
        }

        if (inOrStatement) {
            this.currentOrQueryObject.candidateUser = candidateUser;
        } else {
            this.candidateUser = candidateUser;
        }
        return this;
    }

    @Override
    public HistoricTaskInstanceQuery taskCandidateGroup(String candidateGroup) {
        if (candidateGroup == null) {
            throw new ActivitiIllegalArgumentException("Candidate group is null");
        }

        if (candidateGroups != null) {
            throw new ActivitiIllegalArgumentException("Invalid query usage: cannot set both candidateGroup and candidateGroupIn");
        }

        if (inOrStatement) {
            this.currentOrQueryObject.candidateGroup = candidateGroup;
        } else {
            this.candidateGroup = candidateGroup;
        }
        return this;
    }

    @Override
    public HistoricTaskInstanceQuery taskCandidateGroupIn(List<String> candidateGroups) {
        if (candidateGroups == null) {
            throw new ActivitiIllegalArgumentException("Candidate group list is null");

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Guard with if (candidateGroup != null) before calling taskCandidateGroup and omit the filter otherwise.
  2. Resolve the group earlier and abort the query build with a domain-specific error if the group is mandatory.
  3. Use taskCandidateGroupIn(list) with a populated list if the filter may come from several groups.

Example fix

// before
String group = resolveGroup(tenantId);
query.taskCandidateGroup(group); // throws when unresolved

// after
String group = resolveGroup(tenantId);
if (group != null) {
    query.taskCandidateGroup(group);
}
Defensive patterns

Strategy: validation

Validate before calling

// Java
if (candidateGroup != null && !candidateGroup.isEmpty()) {
    query.taskCandidateGroup(candidateGroup);
}

Try / catch

try {
    query.taskCandidateGroup(group);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    logger.warn("No candidate group resolved; skipping group filter");
}

Prevention

When it happens

Trigger: Calling taskCandidateGroup(null), typically when the group id comes from an unset configuration value, request parameter, or lookup that returned null.

Common situations: Tenant/group resolution code that fails to resolve a group and still builds the query; optional group filter forwarded as null from an API layer.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/621687ce01190041. Report an issue: GitHub.