{"record":{"id":"cb9ab3368ddcccda","repo":"flowable/flowable-engine","slug":"only-one-value-of-user-or-group-is-supported-cb9ab3","errorCode":null,"errorMessage":"Only one value of user or group is supported.","messagePattern":"Only one value of user or group is supported\\.","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":400,"severity":"error","filePath":"modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceIdentityLinkCollectionResource.java","lineNumber":80,"sourceCode":"            notes = \"Note that the groupId in Response Body will always be null, as it’s only possible to involve users with a process-instance.\",\n            code = 201)\n    @ApiResponses(value = {\n            @ApiResponse(code = 201, message = \"Indicates the process instance was found and the link is created.\"),\n            @ApiResponse(code = 400, message = \"Indicates the requested body did not contain a userId or a type.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested process instance was not found.\")\n    })\n    @PostMapping(value = \"/runtime/process-instances/{processInstanceId}/identitylinks\", produces = \"application/json\")\n    @ResponseStatus(HttpStatus.CREATED)\n    public RestIdentityLink createIdentityLink(@ApiParam(name = \"processInstanceId\") @PathVariable String processInstanceId, @RequestBody RestIdentityLink identityLink) {\n\n        ProcessInstance processInstance = getProcessInstanceFromRequestWithoutAccessCheck(processInstanceId);\n\n        if (identityLink.getGroup() == null && identityLink.getUser() == null) {\n            throw new FlowableIllegalArgumentException(\"User or group are required.\");\n        }\n        \n        if (StringUtils.isEmpty(identityLink.getGroup()) && StringUtils.isEmpty(identityLink.getUser())) {\n            throw new FlowableIllegalArgumentException(\"Only one value of user or group is supported.\");\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.createProcessInstanceIdentityLink(processInstance, identityLink);\n        }\n\n        if (StringUtils.isNotEmpty(identityLink.getGroup())) {\n            runtimeService.addGroupIdentityLink(processInstance.getId(), identityLink.getGroup(), identityLink.getType());\n            \n        } else {\n            runtimeService.addUserIdentityLink(processInstance.getId(), identityLink.getUser(), identityLink.getType());\n        }\n\n        return restResponseFactory.createRestIdentityLink(identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), null, null, processInstance.getId());","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceIdentityLinkCollectionResource.java#L62-L98","documentation":"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.').","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{\"type\":\"candidate\",\"user\":\"kermit\",\"group\":\"management\"}\n// after (two calls)\n{\"type\":\"candidate\",\"user\":\"kermit\"}\n{\"type\":\"candidate\",\"group\":\"management\"}","handlingStrategy":"validation","validationCode":"boolean hasUser = link.getUser() != null && !link.getUser().isEmpty();\nboolean hasGroup = link.getGroup() != null && !link.getGroup().isEmpty();\nif (hasUser == hasGroup) {\n    throw new IllegalArgumentException(\"Set exactly one of 'user' or 'group'\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    restClient.createIdentityLink(instanceId, link);\n} catch (HttpClientErrorException.BadRequest e) {\n    if (e.getResponseBodyAsString().contains(\"Only one value of user or group\")) {\n        // split into two calls\n    }\n}","preventionTips":["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."],"tags":["rest-api","identity-link","validation","java"],"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"}