flowable/flowable-engine · error · FlowableIllegalArgumentException

TenantId can only be used with either caseDefinitionKey.

Error message

TenantId can only be used with either caseDefinitionKey.

What it means

A tenantId in a case instance start request can only be combined with caseDefinitionKey (so the engine resolves the latest definition of that key within the tenant). If tenantId is set together with caseDefinitionId, createCaseInstance throws FlowableIllegalArgumentException (HTTP 400) because a definition id already pins the definition and tenant ambiguity is not resolvable.

Solutions

  1. Drop tenantId from the request and use only caseDefinitionId
  2. Or drop caseDefinitionId and use caseDefinitionKey plus tenantId so the tenant-scoped latest version is started
  3. Adjust client/interceptor code that automatically injects tenantId into all start requests

Example fix

// before
{"caseDefinitionId":"myCase:1:4", "tenantId":"tenantA"}
// after
{"caseDefinitionKey":"myCase", "tenantId":"tenantA"}
Defensive patterns

Strategy: validation

Validate before calling

if (request.isTenantSet() && request.getCaseDefinitionId() != null) {
    throw new IllegalArgumentException("tenantId cannot be combined with caseDefinitionId; use caseDefinitionKey");
}

Type guard

null

Try / catch

try {
    startCase(request);
} catch (HttpClientErrorException.BadRequest e) {
    if (e.getResponseBodyAsString().contains("TenantId can only be used with either caseDefinitionKey")) {
        request.setCaseDefinitionId(null);
        request.setCaseDefinitionKey(resolveKeyFromId(request));
    }
}

Prevention

When it happens

Trigger: POST /cmmn-runtime/case-instances with request.isTenantSet() == true and caseDefinitionId != null.

Common situations: Multi-tenant clients that blanket-add tenantId to every start request, copy-pasted payloads combining all optional fields, framework code that injects tenant context into requests that use definition ids.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/runtime/caze/CaseInstanceCollectionResource.java:344

    })
    @PostMapping(value = "/cmmn-runtime/case-instances", produces = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public CaseInstanceResponse createCaseInstance(@RequestBody CaseInstanceCreateRequest request) {

        if (request.getCaseDefinitionId() == null && request.getCaseDefinitionKey() == null) {
            throw new FlowableIllegalArgumentException("Either caseDefinitionId or caseDefinitionKey is required.");
        }

        int paramsSet = ((request.getCaseDefinitionId() != null) ? 1 : 0) + ((request.getCaseDefinitionKey() != null) ? 1 : 0);

        if (paramsSet > 1) {
            throw new FlowableIllegalArgumentException("Only one of caseDefinitionId or caseDefinitionKey should be set.");
        }

        if (request.isTenantSet()) {
            // Tenant-id can only be used with either key or message
            if (request.getCaseDefinitionId() != null) {
                throw new FlowableIllegalArgumentException("TenantId can only be used with either caseDefinitionKey.");
            }
        }

        Map<String, Object> startVariables = null;
        Map<String, Object> transientVariables = null;
        Map<String, Object> startFormVariables = null;
        if (request.getStartFormVariables() != null) {
            startFormVariables = new HashMap<>();
            for (RestVariable variable : request.getStartFormVariables()) {
                if (variable.getName() == null) {
                    throw new FlowableIllegalArgumentException("Variable name is required.");
                }
                startFormVariables.put(variable.getName(), restResponseFactory.getVariableValue(variable));
            }

        }

        if (request.getVariables() != null) {

View on GitHub (pinned to d6d39ce1c6)