flowable/flowable-engine · error · ActivitiIllegalArgumentException

Candidate group list is empty

Error message

Candidate group list is empty

What it means

TaskQueryImpl.taskCandidateGroupIn(List<String>) throws ActivitiIllegalArgumentException when the candidateGroups list is empty. An empty IN list would generate invalid or meaningless SQL (IN ()), so the engine rejects it at query-build time. Callers must pass at least one candidate group.

Solutions

  1. Check list.isEmpty() before calling taskCandidateGroupIn and handle the no-groups case explicitly (e.g. skip the candidate-group filter or return an empty result by policy).
  2. Combine the null and empty checks into one guard helper reused by all query builders.
  3. Catch ActivitiIllegalArgumentException around query construction and report that no candidate groups were supplied.

Example fix

// before
taskQuery.taskCandidateGroupIn(userGroups); // may be empty

// after
if (userGroups != null && !userGroups.isEmpty()) {
    taskQuery.taskCandidateGroupIn(userGroups);
} else {
    return Collections.emptyList(); // policy: no groups => no candidate tasks
}
Defensive patterns

Strategy: validation

Validate before calling

if (candidateGroups == null || candidateGroups.isEmpty()) {
    return Collections.emptyList(); // or skip the filter
}
taskQuery.taskCandidateGroupIn(candidateGroups);

Type guard

boolean isNonEmpty(List<String> list) { return list != null && !list.isEmpty(); }

Try / catch

try {
    return taskQuery.taskCandidateGroupIn(groups).list();
} catch (ActivitiIllegalArgumentException e) {
    log.warn("Empty candidate group filter rejected: {}", e.getMessage());
    return Collections.emptyList();
}

Prevention

When it happens

Trigger: Calling taskQuery.taskCandidateGroupIn(new ArrayList<>()) or passing a collection that was filtered down to zero elements before the query was built.

Common situations: A user belongs to no groups, group filtering removed all entries, or a CSV/config value split into zero tokens; the query is then built unconditionally.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/TaskQueryImpl.java:556

        if (orActive) {
            currentOrQueryObject.bothCandidateAndAssigned = true;
            currentOrQueryObject.userIdForCandidateAndAssignee = userIdForCandidateAndAssignee;
        } else {
            this.bothCandidateAndAssigned = true;
            this.userIdForCandidateAndAssignee = userIdForCandidateAndAssignee;
        }

        return this;
    }

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

        if (candidateGroups.isEmpty()) {
            throw new ActivitiIllegalArgumentException("Candidate group list is empty");
        }

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

        if (orActive) {
            currentOrQueryObject.candidateGroups = candidateGroups;
        } else {
            this.candidateGroups = candidateGroups;
        }
        return this;
    }

    @Override
    public TaskQuery taskTenantId(String tenantId) {
        if (tenantId == null) {
            throw new ActivitiIllegalArgumentException("task tenant id is null");

View on GitHub (pinned to d6d39ce1c6)