flowable/flowable-engine · error · FlowableIllegalArgumentException

Candidate user is null

Error message

Candidate user is null

What it means

HistoricTaskInstanceQueryImpl.taskCandidateUser(String) throws FlowableIllegalArgumentException when the candidateUser argument is null. Flowable query builders validate required filter values eagerly so that an invalid query fails at construction time rather than producing an SQL error or empty results at execution. The field is only assigned after the null check passes.

Solutions

  1. Pass a non-null user id string to taskCandidateUser(); verify the value with a null check first.
  2. If the candidate user is optional, skip calling taskCandidateUser() entirely instead of passing null.
  3. If the candidate user is unknown but identity links matter, use taskCandidateGroup()/taskCandidateGroupIn() or taskInvolvedUser() filters instead.

Example fix

// before
String user = task.getCandidateUser();
query.taskCandidateUser(user); // NPE-free but throws if user is null

// after
if (task.getCandidateUser() != null) {
    query.taskCandidateUser(task.getCandidateUser());
}
Defensive patterns

Strategy: validation

Validate before calling

if (candidateUser == null || candidateUser.isEmpty()) {
    throw new IllegalArgumentException("candidateUser must be a non-empty user id");
}
historicTaskInstanceQuery.taskCandidateUser(candidateUser);

Type guard

boolean isValidUser(String userId) {
    return userId != null && !userId.trim().isEmpty();
}

Try / catch

try {
    query.taskCandidateUser(candidateUser);
} catch (FlowableIllegalArgumentException e) {
    // log and rebuild the query without this filter
    log.warn("Invalid candidateUser filter: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Calling historicTaskInstanceQuery.taskCandidateUser(null), typically because a variable holding the user id was null (e.g. userTask.getCandidateUser() returned null, or an unresolved request parameter was passed straight through).

Common situations: Passing through a task assignment value that was never set in the BPMN model; wiring a REST query parameter that the client omitted; refactoring where a default user constant was removed and the variable defaults to null.

Related errors


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

Appendix: source

Thrown at modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java:1890

    @Override
    public HistoricTaskInstanceQuery taskFormKey(String formKey) {
        if (formKey == null) {
            throw new FlowableIllegalArgumentException("Task formKey is null");
        }

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

    @Override
    public HistoricTaskInstanceQuery taskCandidateUser(String candidateUser) {
        if (candidateUser == null) {
            throw new FlowableIllegalArgumentException("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 FlowableIllegalArgumentException("Candidate group is null");
        }

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

View on GitHub (pinned to d6d39ce1c6)