flowable/flowable-engine · error · ActivitiIllegalArgumentException
identityId is null
Error message
identityId is null
What it means
AddIdentityLinkCmd.validateParams rejects a null identityId unless the link is a group link or an ASSIGNEE/OWNER link (where the id may legitimately be null to unset). Flowable throws ActivitiIllegalArgumentException because the link would reference neither a user nor a group.
Solutions
- Pass a non-null userId/groupId, or use type IdentityLinkType.ASSIGNEE or OWNER with null to clear
- Use IDENTITY_GROUP (2) as identityIdType if you intended a group link with null user id
- Validate the resolved user id before invoking the command
Example fix
// before
taskService.addUserIdentityLink(taskId, userId, IdentityLinkType.CANDIDATE); // userId null
// after
if (userId == null) {
taskService.setAssignee(taskId, null); // to clear, use assignee
} else {
taskService.addUserIdentityLink(taskId, userId, IdentityLinkType.CANDIDATE);
} Defensive patterns
Strategy: validation
Validate before calling
boolean idMayBeNull = IdentityLinkType.ASSIGNEE.equals(type) || IdentityLinkType.OWNER.equals(type);
if (identityId == null && !idMayBeNull) {
throw new IllegalArgumentException("identityId required for type " + type);
} Try / catch
try {
taskService.addUserIdentityLink(taskId, userId, type);
} catch (ActivitiIllegalArgumentException e) {
if (e.getMessage().equals("identityId is null")) {
taskService.setAssignee(taskId, null); // clearing via assignee
} else { throw e; }
} Prevention
- Remember only ASSIGNEE/OWNER types may have a null identityId
- Resolve and validate user ids before passing them on
- Do not confuse the userId and type parameter positions
When it happens
Trigger: Calling taskService.addUserIdentityLink(taskId, null, type) with a non-assignee/owner type; AddIdentityLinkCmd constructed with identityId == null and identityIdType == IDENTITY_USER with a type other than ASSIGNEE/OWNER.
Common situations: Swapping the userId and type parameters by mistake; the user id lookup returned null upstream and was passed along; intending to clear an assignee but using a candidate/participant type instead.
Related errors
- taskId is null
- type is required when adding a new task identity link
- appDefinitionId is null
- identityId is null
- processDefinitionId is null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/12afb91857c7bc4c.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/cmd/AddIdentityLinkCmd.java:57
this.taskId = taskId;
this.identityId = identityId;
this.identityIdType = identityIdType;
this.identityType = identityType;
}
protected void validateParams(String taskId, String identityId, int identityIdType, String identityType) {
if (taskId == null) {
throw new ActivitiIllegalArgumentException("taskId is null");
}
if (identityType == null) {
throw new ActivitiIllegalArgumentException("type is required when adding a new task identity link");
}
if (identityId == null && (identityIdType == IDENTITY_GROUP ||
(!IdentityLinkType.ASSIGNEE.equals(identityType) && !IdentityLinkType.OWNER.equals(identityType)))) {
throw new ActivitiIllegalArgumentException("identityId is null");
}
if (identityIdType != IDENTITY_USER && identityIdType != IDENTITY_GROUP) {
throw new ActivitiIllegalArgumentException("identityIdType allowed values are 1 and 2");
}
}
@Override
protected Void execute(CommandContext commandContext, TaskEntity task) {
boolean assignedToNoOne = false;
if (IdentityLinkType.ASSIGNEE.equals(identityType)) {
task.setAssignee(identityId, true, true);
assignedToNoOne = identityId == null;
} else if (IdentityLinkType.OWNER.equals(identityType)) {
task.setOwner(identityId, true);
} else if (IDENTITY_USER == identityIdType) {
task.addUserIdentityLink(identityId, identityType);View on GitHub (pinned to d6d39ce1c6)