flowable/flowable-engine · error · FlowableIllegalArgumentException
Only one value of user or group is supported.
Error message
Only one value of user or group is supported.
What it means
An identity link may reference a user XOR a group, never both. After passing the null-check, createIdentityLink rejects requests where both fields are present (empty counts as present-and-invalid here), throwing FlowableIllegalArgumentException('Only one value of user or group is supported.').
Solutions
- Send only one of "user" or "group"; if you need both, issue two separate identity-link calls.
- Omit (don't empty-string) the unused field in your JSON body.
- Add client-side validation: reject payloads where both user and group are non-empty before the call.
Example fix
// before
{"type":"candidate","user":"kermit","group":"management"}
// after (two calls)
{"type":"candidate","user":"kermit"}
{"type":"candidate","group":"management"} Defensive patterns
Strategy: validation
Validate before calling
boolean hasUser = link.getUser() != null && !link.getUser().isEmpty();
boolean hasGroup = link.getGroup() != null && !link.getGroup().isEmpty();
if (hasUser == hasGroup) {
throw new IllegalArgumentException("Set exactly one of 'user' or 'group'");
} Try / catch
try {
restClient.createIdentityLink(instanceId, link);
} catch (HttpClientErrorException.BadRequest e) {
if (e.getResponseBodyAsString().contains("Only one value of user or group")) {
// split into two calls
}
} Prevention
- Enforce XOR user/group in your request builder.
- Never send empty strings; omit unused fields entirely.
- Document that user+group links require two separate calls.
When it happens
Trigger: POST /runtime/process-instances/{id}/identitylinks with {"type":"candidate","user":"kermit","group":"management"} — both user and group supplied. Also triggered when empty strings are sent for both fields. Thrown at ProcessInstanceIdentityLinkCollectionResource.java:80.
Common situations: Client code that always populates both DTO fields from form input; copying a user-link payload and adding a group; frameworks that send empty strings "" rather than omitting fields.
Related errors
- The identity link type is required.
- User or group are required.
- A group or a user is required to create an identity link.
- Identity link family should be 'users' or 'groups'.
- Identity link family should be 'users' or 'groups'.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/cb9ab3368ddcccda.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceIdentityLinkCollectionResource.java:80
notes = "Note that the groupId in Response Body will always be null, as it’s only possible to involve users with a process-instance.",
code = 201)
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Indicates the process instance was found and the link is created."),
@ApiResponse(code = 400, message = "Indicates the requested body did not contain a userId or a type."),
@ApiResponse(code = 404, message = "Indicates the requested process instance was not found.")
})
@PostMapping(value = "/runtime/process-instances/{processInstanceId}/identitylinks", produces = "application/json")
@ResponseStatus(HttpStatus.CREATED)
public RestIdentityLink createIdentityLink(@ApiParam(name = "processInstanceId") @PathVariable String processInstanceId, @RequestBody RestIdentityLink identityLink) {
ProcessInstance processInstance = getProcessInstanceFromRequestWithoutAccessCheck(processInstanceId);
if (identityLink.getGroup() == null && identityLink.getUser() == null) {
throw new FlowableIllegalArgumentException("User or group are required.");
}
if (StringUtils.isEmpty(identityLink.getGroup()) && StringUtils.isEmpty(identityLink.getUser())) {
throw new FlowableIllegalArgumentException("Only one value of user or group is supported.");
}
if (identityLink.getType() == null) {
throw new FlowableIllegalArgumentException("The identity link type is required.");
}
if (restApiInterceptor != null) {
restApiInterceptor.createProcessInstanceIdentityLink(processInstance, identityLink);
}
if (StringUtils.isNotEmpty(identityLink.getGroup())) {
runtimeService.addGroupIdentityLink(processInstance.getId(), identityLink.getGroup(), identityLink.getType());
} else {
runtimeService.addUserIdentityLink(processInstance.getId(), identityLink.getUser(), identityLink.getType());
}
return restResponseFactory.createRestIdentityLink(identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), null, null, processInstance.getId());View on GitHub (pinned to d6d39ce1c6)