flowable/flowable-engine · error · FlowableIllegalArgumentException
Invalid query usage: cannot set both candidateGroup and…
Error message
Invalid query usage: cannot set both candidateGroup and candidateGroupIn
What it means
taskCandidateGroup(String) throws this error when the query already has candidateGroups (from taskCandidateGroupIn) set. The two filters are mutually exclusive: candidateGroup matches exactly one group while candidateGroupIn matches any of a list, and the SQL generator supports only one form per query object.
Solutions
- Choose one filter: remove either the taskCandidateGroup(...) call or the taskCandidateGroupIn(...) call on the same query.
- Use a fresh HistoricTaskInstanceQuery instance for each distinct filter combination.
- Represent a single group as a one-element list and use taskCandidateGroupIn(Collections.singletonList(group)) exclusively.
Example fix
// before
query.taskCandidateGroupIn(groups);
query.taskCandidateGroup("backoffice"); // throws
// after
List<String> effectiveGroups = new ArrayList<>(groups);
effectiveGroups.add("backoffice");
query.taskCandidateGroupIn(effectiveGroups); Defensive patterns
Strategy: validation
Validate before calling
// choose ONE form before building the query
boolean useGroupList = groupList != null && !groupList.isEmpty();
if (useGroupList) {
query.taskCandidateGroupIn(groupList);
} else if (candidateGroup != null) {
query.taskCandidateGroup(candidateGroup);
} Type guard
boolean canSetCandidateGroup(HistoricTaskInstanceQueryImpl q) {
return q.candidateGroups == null; // group list not yet set
} Try / catch
try {
query.taskCandidateGroup(candidateGroup);
} catch (FlowableIllegalArgumentException e) {
throw new IllegalStateException(
"Query already filters by candidateGroupIn; remove one of the two group filters", e);
} Prevention
- Standardize on taskCandidateGroupIn(Collections.singletonList(g)) so only one API is used.
- Build group filters from a single accumulation point instead of scattered setter calls.
- Create a fresh query instance per request/filter combination.
When it happens
Trigger: Chaining .taskCandidateGroupIn(Arrays.asList("a","b")).taskCandidateGroup("a") — or the reverse order with taskCandidateGroup called before taskCandidateGroupIn — on the same HistoricTaskInstanceQuery instance.
Common situations: Building query filters dynamically where different code paths each set a candidate-group criterion; UIs that let users pick either a single group or a group list but accumulate conditions instead of replacing them.
Related errors
- Invalid query usage: cannot set both candidateGroupIn and…
- Candidate group is null
- Candidate group list is empty
- Candidate group list is null
- Candidate user is null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/51060bcf037bebec.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/HistoricTaskInstanceQueryImpl.java:1908
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");
}
if (inOrStatement) {
this.currentOrQueryObject.candidateGroup = candidateGroup;
} else {
this.candidateGroup = candidateGroup;
}
return this;
}
@Override
public HistoricTaskInstanceQuery taskCandidateGroupIn(Collection<String> candidateGroups) {
if (candidateGroups == null) {
throw new FlowableIllegalArgumentException("Candidate group list is null");
}
if (candidateGroups.isEmpty()) {
throw new FlowableIllegalArgumentException("Candidate group list is empty");View on GitHub (pinned to d6d39ce1c6)