{"record":{"id":"3eebadb33431cfdd","repo":"flowable/flowable-engine","slug":"only-one-of-user-or-group-can-be-used-to-create-an-3eebad","errorCode":null,"errorMessage":"Only one of user or group can be used to create an identity link.","messagePattern":"Only one of user or group can be used to create an identity link\\.","errorType":"http","errorClass":"FlowableIllegalArgumentException","httpStatus":400,"severity":"error","filePath":"modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/task/TaskIdentityLinkCollectionResource.java","lineNumber":76,"sourceCode":"\n    @ApiOperation(value = \"Create an identity link on a task\", tags = { \"Task Identity Links\" }, nickname = \"createTaskInstanceIdentityLinks\",\n            notes = \"It is possible to add either a user or a group.\", code = 201)\n    @ApiResponses(value = {\n            @ApiResponse(code = 201, message = \"Indicates the task was found and the identity link was created.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested task was not found or the task does not have the requested identityLink. The status contains additional information about this error.\")\n    })\n    @PostMapping(value = \"/cmmn-runtime/tasks/{taskId}/identitylinks\", produces = \"application/json\")\n    @ResponseStatus(HttpStatus.CREATED)\n    public RestIdentityLink createIdentityLink(@ApiParam(name = \"taskId\") @PathVariable(\"taskId\") String taskId, @RequestBody RestIdentityLink identityLink) {\n\n        Task task = getTaskFromRequestWithoutAccessCheck(taskId);\n\n        if (identityLink.getGroup() == null && identityLink.getUser() == null) {\n            throw new FlowableIllegalArgumentException(\"A group or a user is required to create an identity link.\");\n        }\n\n        if (identityLink.getGroup() != null && identityLink.getUser() != null) {\n            throw new FlowableIllegalArgumentException(\"Only one of user or group can be used to create an identity link.\");\n        }\n\n        if (identityLink.getType() == null) {\n            throw new FlowableIllegalArgumentException(\"The identity link type is required.\");\n        }\n\n        if (restApiInterceptor != null) {\n            restApiInterceptor.createTaskIdentityLink(task, identityLink);\n        }\n\n        if (identityLink.getGroup() != null) {\n            taskService.addGroupIdentityLink(task.getId(), identityLink.getGroup(), identityLink.getType());\n        } else {\n            taskService.addUserIdentityLink(task.getId(), identityLink.getUser(), identityLink.getType());\n        }\n\n        return restResponseFactory.createRestIdentityLink(identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), task.getId(), null, null);\n    }","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/task/TaskIdentityLinkCollectionResource.java#L58-L94","documentation":"The Flowable CMMN REST API rejects an identity link creation request when the payload specifies both a user and a group. An identity link associates exactly one principal (a user OR a group) with a task, so a request carrying both is ambiguous and cannot be represented.","triggerScenarios":"POSTing to /cmmn-runtime/tasks/{taskId}/identitylinks with a JSON body where both 'user' and 'group' fields are non-null.","commonSituations":"Client code copies an object with both fields populated; a form collects both assignee-user and candidate-group and serializes them together; a generic 'owner' object is mapped into the request body without picking one principal.","solutions":["Remove the 'user' or the 'group' field from the request body so exactly one is set","If the intent is a user link, set only {\"user\": \"...\", \"type\": \"candidate\"}; for a group link set only {\"group\": \"...\", \"type\": \"candidate\"}","Fix client-side serialization so mutually exclusive principals never both appear in the payload"],"exampleFix":"// before\nPOST /cmmn-runtime/tasks/123/identitylinks\n{\"user\":\"john\",\"group\":\"management\",\"type\":\"candidate\"}\n// after\nPOST /cmmn-runtime/tasks/123/identitylinks\n{\"group\":\"management\",\"type\":\"candidate\"}","handlingStrategy":"validation","validationCode":"function canCreateIdentityLink(body) {\n  const count = [body.user, body.group].filter(Boolean).length;\n  return count === 1 && typeof body.type === 'string';\n}","typeGuard":"function hasExactlyOnePrincipal(b) {\n  return (b.user != null) !== (b.group != null);\n}","tryCatchPattern":"try {\n  await post(`/cmmn-runtime/tasks/${taskId}/identitylinks`, body);\n} catch (e) {\n  if (e.status === 400 && /Only one of user or group/.test(e.body.message)) {\n    throw new Error('Send either user or group, not both');\n  }\n  throw e;\n}","preventionTips":["Model the payload with a discriminated union: either {user} or {group}, never both","Clear the sibling field when one principal is chosen in UI code","Unit-test request serialization with both fields set to catch regressions"],"tags":["rest-api","validation","identity-link"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}