flowable/flowable-engine · warning · FlowableIllegalArgumentException

Only one of user or group can be used to create an identity

Error message

Only one of user or group can be used to create an identity link.

What it means

Immediately after the null-check, createIdentityLink rejects bodies where BOTH 'user' and 'group' are set: a Flowable identity link is either to a user or to a group, never both. Setting both is ambiguous, so FlowableIllegalArgumentException is thrown (HTTP 400).

Source

Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/repository/CaseDefinitionIdentityLinkCollectionResource.java:78

    @ApiOperation(value = "Add a candidate starter to a case definition", tags = { "Case Definitions" },
            notes = "It is possible to add either a user or a group.", code = 201)
    @ApiResponses(value = {
            @ApiResponse(code = 201, message = "Indicates the case definition was found and the identity link was created."),
            @ApiResponse(code = 400, message = "Indicates the body does not contain the correct information."),
            @ApiResponse(code = 404, message = "Indicates the requested case definition was not found.")
    })
    @PostMapping(value = "/cmmn-repository/case-definitions/{caseDefinitionId}/identitylinks", produces = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public RestIdentityLink createIdentityLink(@ApiParam(name = "caseDefinitionId") @PathVariable String caseDefinitionId, @RequestBody RestIdentityLink identityLink) {

        CaseDefinition caseDefinition = getCaseDefinitionFromRequestWithoutAccessCheck(caseDefinitionId);

        if (identityLink.getGroup() == null && identityLink.getUser() == null) {
            throw new FlowableIllegalArgumentException("A group or a user is required to create an identity link.");
        }

        if (identityLink.getGroup() != null && identityLink.getUser() != null) {
            throw new FlowableIllegalArgumentException("Only one of user or group can be used to create an identity link.");
        }

        if (restApiInterceptor != null) {
            restApiInterceptor.createCaseDefinitionIdentityLink(caseDefinition, identityLink);
        }

        if (identityLink.getGroup() != null) {
            repositoryService.addCandidateStarterGroup(caseDefinition.getId(), identityLink.getGroup());
        } else {
            repositoryService.addCandidateStarterUser(caseDefinition.getId(), identityLink.getUser());
        }

        // Always candidate for case definition. User-provided value is ignored
        identityLink.setType(IdentityLinkType.CANDIDATE);

        return restResponseFactory.createRestIdentityLink(identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), null, caseDefinition.getId(), null);
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Send exactly one of 'user' or 'group'; issue two separate POSTs if both are needed.
  2. Strip the unused field in the client before serializing the payload.
  3. If the intent is 'user who is member of group', just send the user and let group membership resolve it.

Example fix

// before
{"type":"candidate","user":"john","group":"sales"}
// after
{"type":"candidate","user":"john"}
// second call if needed:
{"type":"candidate","group":"sales"}
Defensive patterns

Strategy: validation

Validate before calling

function validateIdentityLink(link) {
  if (link.user && link.group) throw new Error('Send user OR group, not both');
  if (!link.user && !link.group) throw new Error('Send user or group');
  return true;
}

Type guard

const isExclusiveIdentityLink = (l) =>
  Boolean(l) && (Boolean(l.user) !== Boolean(l.group));

Try / catch

try {
  await post(identityLinksUrl, link);
} catch (e) {
  if (e.response?.status === 400 && /Only one of user or group/.test(e.response.data?.message)) {
    // split into two posts, one per subject
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /cmmn-repository/case-definitions/{caseDefinitionId}/identity-links with a body containing both fields, e.g. {"type":"candidate","user":"john","group":"sales"}.

Common situations: Clients that echo back a whole identity-link object populated with defaults; forms that always submit both inputs; generic sync tools mapping an ACL entry with both subject types onto one link.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/da5d8a6d169d57ad. Report an issue: GitHub.