{"record":{"id":"b9b5378c6dba1525","repo":"flowable/flowable-engine","slug":"userid-and-groupid-cannot-both-be-null-b9b537","errorCode":null,"errorMessage":"userId and groupId cannot both be null","messagePattern":"userId and groupId cannot both be null","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/DeleteIdentityLinkCmd.java","lineNumber":69,"sourceCode":"    }\n\n    protected void validateParams(String userId, String groupId, String type, String taskId) {\n        if (taskId == null) {\n            throw new FlowableIllegalArgumentException(\"taskId is null\");\n        }\n\n        if (type == null) {\n            throw new FlowableIllegalArgumentException(\"type is required when adding a new task identity link\");\n        }\n\n        // Special treatment for assignee and owner: group cannot be used and userId may be null\n        if (IdentityLinkType.ASSIGNEE.equals(type) || IdentityLinkType.OWNER.equals(type)) {\n            if (groupId != null) {\n                throw new FlowableIllegalArgumentException(\"Incompatible usage: cannot use type '\" + type + \"' together with a groupId\");\n            }\n        } else {\n            if (userId == null && groupId == null) {\n                throw new FlowableIllegalArgumentException(\"userId and groupId cannot both be null\");\n            }\n        }\n    }\n\n    @Override\n    protected Void execute(CommandContext commandContext, TaskEntity task) {\n        if (task.getProcessDefinitionId() != null && Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, task.getProcessDefinitionId())) {\n            Flowable5CompatibilityHandler compatibilityHandler = Flowable5Util.getFlowable5CompatibilityHandler();\n            compatibilityHandler.deleteIdentityLink(taskId, userId, groupId, type);\n            return null;\n        }\n\n        if (IdentityLinkType.ASSIGNEE.equals(type)) {\n            TaskHelper.changeTaskAssignee(task, null);\n        } else if (IdentityLinkType.OWNER.equals(type)) {\n            TaskHelper.changeTaskOwner(task, null);\n        } else {\n            IdentityLinkUtil.deleteTaskIdentityLinks(task, userId, groupId, type);","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/DeleteIdentityLinkCmd.java#L51-L87","documentation":"DeleteIdentityLinkCmd requires at least one identity to act on. Flowable throws this FlowableIllegalArgumentException during validateParams when both userId and groupId are null and the identity link type is not ASSIGNEE/OWNER, since there would be no identity link to delete.","triggerScenarios":"Calling IdentityService.deleteTaskIdentityLink(taskId, null, null, type) or RuntimeService.deleteProcessInstanceIdentityLink(processInstanceId, null, null, type) with both identity arguments null for a non-assignee/owner type (e.g. 'candidate').","commonSituations":"Passing optional variables from request payloads where neither user nor group was provided; forgetting that 'candidate' links are either user or group scoped; copying a delete call from an add call but clearing both ids.","solutions":["Supply either userId or groupId (one may be null, but not both) in the delete call.","If the intent was to remove an assignee/owner, pass the user id and type ASSIGNEE or OWNER.","Guard the call site: skip the API call when both values are null, or reject the input earlier."],"exampleFix":"// before\nidentityService.deleteTaskIdentityLink(taskId, null, null, IdentityLinkType.CANDIDATE);\n// after\nif (userId != null || groupId != null) {\n    identityService.deleteTaskIdentityLink(taskId, userId, groupId, IdentityLinkType.CANDIDATE);\n}","handlingStrategy":"validation","validationCode":"// Java\nif (userId == null && groupId == null) {\n    throw new IllegalArgumentException(\"Provide userId or groupId before deleting the identity link\");\n}\nidentityService.deleteTaskIdentityLink(taskId, userId, groupId, type);","typeGuard":"boolean hasIdentity(String userId, String groupId) { return userId != null || groupId != null; }","tryCatchPattern":"try {\n    identityService.deleteTaskIdentityLink(taskId, userId, groupId, type);\n} catch (FlowableIllegalArgumentException e) {\n    log.warn(\"Invalid identity link delete request: {}\", e.getMessage());\n}","preventionTips":["Wrap identity-link delete operations in a helper that asserts exactly one of userId/groupId is present.","Validate request DTOs (e.g. @AssertTrue on at-least-one-of constraints) before reaching the Flowable API.","Remember candidate links are user OR group scoped — resolve which one before deleting."],"tags":["flowable","identity-link","null-argument","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}