flowable/flowable-engine · error · ActivitiIllegalArgumentException
Invalid query usage: cannot set both candidateGroupIn and…
Error message
Invalid query usage: cannot set both candidateGroupIn and candidateGroup
What it means
TaskQueryImpl throws ActivitiIllegalArgumentException when both taskCandidateGroup(String) (candidateGroup) and taskCandidateGroupIn(List<String>) (candidateGroups) have been set on the same query. The two filters are mutually exclusive: one targets a single group, the other a set, and combining them would produce ambiguous semantics.
Solutions
- Make the two options mutually exclusive in the caller: apply candidateGroup only when candidateGroups is absent, and vice versa (if/else, not both).
- Reset or create a fresh TaskQuery instance when the filter mode changes.
- Catch ActivitiIllegalArgumentException and map it to a 400 response telling the client to send only one group filter.
Example fix
// before
query.taskCandidateGroup(request.getGroup());
query.taskCandidateGroupIn(request.getGroups());
// after
if (request.getGroup() != null) {
query.taskCandidateGroup(request.getGroup());
} else if (request.getGroups() != null && !request.getGroups().isEmpty()) {
query.taskCandidateGroupIn(request.getGroups());
} Defensive patterns
Strategy: validation
Validate before calling
if (group != null && groups != null) {
throw new IllegalArgumentException("Use either candidateGroup or candidateGroupIn, not both");
} Type guard
boolean hasExactlyOneGroupFilter(String group, List<String> groups) { return (group == null) != (groups == null); } Try / catch
try {
return taskQuery.list();
} catch (ActivitiIllegalArgumentException e) {
throw new BadRequestException("Send only one of 'group' or 'groups' filters");
} Prevention
- Enforce mutual exclusivity in the API layer (single 'group filter' field with a mode)
- Never apply all non-null request fields additively to a query
- Document that candidateGroup and candidateGroupIn cannot be combined
When it happens
Trigger: Calling taskQuery.taskCandidateGroup("hr") and then taskQuery.taskCandidateGroupIn(groups) (or vice versa) on the same TaskQuery instance, e.g. when filter options are applied additively from request parameters.
Common situations: Generic query-builder code that applies every non-null request field; a UI allowing both 'group' and 'any of groups' inputs without making them mutually exclusive; copying an existing query object and adding new filters.
Related errors
- Candidate group list is empty
- Candidate group list is null
- Invalid query usage: cannot set both candidateGroup and…
- Invalid query usage: cannot set both candidateGroup and…
- Invalid query usage: cannot set both candidateGroup and…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/052f40967134606a.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/TaskQueryImpl.java:560
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");
}
if (orActive) {
currentOrQueryObject.tenantId = tenantId;
} else {View on GitHub (pinned to d6d39ce1c6)