flowable/flowable-engine · error · FlowableIllegalArgumentException
userId and groupId cannot both be null
Error message
userId and groupId cannot both be null
What it means
FlowableIllegalArgumentException thrown by AddIdentityLinkForProcessInstanceCmd.validateParams when both userId and groupId are null. An identity link must point at a user, a group, or both; a link to neither is meaningless so the command rejects it after the type check.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/AddIdentityLinkForProcessInstanceCmd.java:63
validateParams(processInstanceId, userId, groupId, type);
this.processInstanceId = processInstanceId;
this.userId = userId;
this.groupId = groupId;
this.type = type;
}
protected void validateParams(String processInstanceId, String userId, String groupId, String type) {
if (processInstanceId == null) {
throw new FlowableIllegalArgumentException("processInstanceId is null");
}
if (type == null) {
throw new FlowableIllegalArgumentException("type is required when adding a new process instance identity link");
}
if (userId == null && groupId == null) {
throw new FlowableIllegalArgumentException("userId and groupId cannot both be null");
}
}
@Override
public Void execute(CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
ExecutionEntity processInstance = executionEntityManager.findById(processInstanceId);
if (processInstance == null) {
throw new FlowableObjectNotFoundException("Cannot find process instance with id " + processInstanceId, ExecutionEntity.class);
}
if (Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, processInstance.getProcessDefinitionId())) {
Flowable5CompatibilityHandler compatibilityHandler = Flowable5Util.getFlowable5CompatibilityHandler();
compatibilityHandler.addIdentityLinkForProcessInstance(processInstanceId, userId, groupId, type);
return null;View on GitHub (pinned to d6d39ce1c6)
Solutions
- Ensure at least one of userId or groupId is a non-null, non-empty string before calling the API.
- Check the upstream lookup that produces userId/groupId (it returned null) and handle the empty case earlier.
- If you truly want a link without user/group, use a different mechanism (e.g. process variable) — identity links require a target.
Example fix
// before
runtimeService.addIdentityLinkForProcessInstance(piId, userId, groupId, IdentityLinkType.CANDIDATE); // both null
// after
if (userId == null && groupId == null) {
throw new IllegalArgumentException("Provide userId or groupId");
}
runtimeService.addIdentityLinkForProcessInstance(piId, userId, groupId, IdentityLinkType.CANDIDATE); Defensive patterns
Strategy: validation
Validate before calling
if ((userId == null || userId.isEmpty()) && (groupId == null || groupId.isEmpty())) {
throw new IllegalArgumentException("identity link requires userId or groupId");
} Type guard
boolean hasTarget(String userId, String groupId) {
return (userId != null && !userId.isEmpty()) || (groupId != null && !groupId.isEmpty());
} Try / catch
try {
runtimeService.addGroupIdentityLink(piId, groupId, IdentityLinkType.CANDIDATE);
} catch (FlowableIllegalArgumentException e) {
if (e.getMessage().contains("cannot both be null")) {
// resolve the user/group upstream before retrying
}
throw e;
} Prevention
- Resolve userId/groupId from lookups that fail loudly instead of returning null.
- Make userId or groupId a required field in the API that triggers identity-link creation.
- Prefer using addUserIdentityLink or addGroupIdentityLink so only one target can be passed.
When it happens
Trigger: Calling RuntimeService.addUserIdentityLink / addGroupIdentityLink / addIdentityLinkForProcessInstance with a null type-safe type but null userId and null groupId, e.g. addIdentityLinkForProcessInstance(pid, null, null, "candidate").
Common situations: Resolving assignee/group from external data (LDAP lookup, form field) that returned nothing; both userId and groupId sourced from optional request fields left empty; wiring bug passing wrong variables into the command.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- type is required when adding a new process instance identity
- userId and groupId cannot both be null
- Error retrieving app engine info
- No deployment id available
- No resource name available
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b3b847ed838600c3.
Report an issue: GitHub.