flowable/flowable-engine · error · ActivitiIllegalArgumentException

Candidate group list is empty

Error message

Candidate group list is empty

What it means

taskCandidateGroupIn(List<String>) rejects an empty list: an IN filter with zero values has no meaning, so the engine throws ActivitiIllegalArgumentException instead of producing a query that matches nothing or everything ambiguously.

Solutions

  1. Check !candidateGroups.isEmpty() and skip the filter call when empty (result: no candidate-group filter).
  2. If an empty selection should return no results by design, short-circuit and skip executing the query instead.
  3. Ensure upstream group resolvers return null/absent rather than an empty list when no filter is intended, and guard accordingly.

Example fix

// before
List<String> groups = userPermissions.getGroups(); // may be empty
query.taskCandidateGroupIn(groups); // throws when empty

// after
List<String> groups = userPermissions.getGroups();
if (groups != null && !groups.isEmpty()) {
    query.taskCandidateGroupIn(groups);
}
Defensive patterns

Strategy: validation

Validate before calling

// Java
if (candidateGroups != null && !candidateGroups.isEmpty()) {
    query.taskCandidateGroupIn(candidateGroups);
} // else: no group filter (or short-circuit for zero results by design)

Try / catch

try {
    query.taskCandidateGroupIn(groups);
} catch (org.activiti.engine.ActivitiIllegalArgumentException e) {
    // decide: skip filter, or return empty result set intentionally
}

Prevention

When it happens

Trigger: Calling taskCandidateGroupIn(Collections.emptyList()) or any list of size 0 on a HistoricTaskInstanceQuery (also inside an or() block).

Common situations: Multi-select UI where no group was chosen but the query is still built; group filter derived from a permission set that is empty for the current user; JDBC/IN-clause habits carried over where empty lists were filtered out downstream.

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/d213746145929e0e. Report an issue: GitHub.

Appendix: source

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

            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");
        }

        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 (inOrStatement) {
            this.currentOrQueryObject.candidateGroups = candidateGroups;
        } else {
            this.candidateGroups = candidateGroups;
        }
        return this;
    }

    @Override
    public HistoricTaskInstanceQuery taskInvolvedUser(String involvedUser) {
        if (inOrStatement) {
            this.currentOrQueryObject.involvedUser = involvedUser;

View on GitHub (pinned to d6d39ce1c6)