flowable/flowable-engine · error · FlowableIllegalArgumentException

TenantId can only be used with either processDefinitionKey…

Error message

TenantId can only be used with either processDefinitionKey or message.

What it means

FlowableIllegalArgumentException thrown by createProcessInstance when the request sets a tenantId (request.isTenantSet() is true) together with processDefinitionId. Tenant resolution happens when looking up a definition by key or message; with an explicit definition id the tenant is already fully determined, so passing a tenantId is contradictory and rejected.

Solutions

  1. Remove tenantId from the request when starting by processDefinitionId
  2. Switch to processDefinitionKey plus tenantId if tenant scoping is required
  3. Have the server-side tenant context resolve the definition key per tenant instead of sending an id
  4. Audit request-building code to only add tenantId for key/message starts

Example fix

// before
{"processDefinitionId":"orderProcess:2:125","tenantId":"acme"}
// after
{"processDefinitionKey":"orderProcess","tenantId":"acme"}
Defensive patterns

Strategy: validation

Validate before calling

if (req.tenantId != null && req.processDefinitionId != null) {
  throw new Error('tenantId cannot be combined with processDefinitionId; use processDefinitionKey or message');
}

Type guard

function tenantUsageValid(req) {
  return !(req?.tenantId != null && req?.processDefinitionId != null);
}

Try / catch

try { await startProcessInstance(req); } catch (e) { if (e.status === 400 && /TenantId can only be used/.test(e.message)) { delete req.processDefinitionId; } throw e; }

Prevention

When it happens

Trigger: POST /runtime/process-instances with both processDefinitionId and tenantId (or the tenantId field populated such that isTenantSet() returns true) in the body.

Common situations: Multi-tenant clients always including tenantId out of habit, generic request builders that attach tenant context to every start call, migrating code from key-based to id-based starts while keeping the tenant field.

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/32c0e30f976dca0f. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ProcessInstanceCollectionResource.java:343

    })
    @PostMapping(value = "/runtime/process-instances", produces = "application/json")
    @ResponseStatus(HttpStatus.CREATED)
    public ProcessInstanceResponse createProcessInstance(@RequestBody ProcessInstanceCreateRequest request) {

        if (request.getProcessDefinitionId() == null && request.getProcessDefinitionKey() == null && request.getMessage() == null) {
            throw new FlowableIllegalArgumentException("Either processDefinitionId, processDefinitionKey or message is required.");
        }

        int paramsSet = ((request.getProcessDefinitionId() != null) ? 1 : 0) + ((request.getProcessDefinitionKey() != null) ? 1 : 0) + ((request.getMessage() != null) ? 1 : 0);

        if (paramsSet > 1) {
            throw new FlowableIllegalArgumentException("Only one of processDefinitionId, processDefinitionKey or message should be set.");
        }

        if (request.isTenantSet()) {
            // Tenant-id can only be used with either key or message
            if (request.getProcessDefinitionId() != null) {
                throw new FlowableIllegalArgumentException("TenantId can only be used with either processDefinitionKey or message.");
            }
        }
        
        Map<String, Object> startVariables = null;
        Map<String, Object> transientVariables = null;
        Map<String, Object> startFormVariables = null;
        if (request.getStartFormVariables() != null && !request.getStartFormVariables().isEmpty()) {
            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 && !request.getVariables().isEmpty()) {
            startVariables = new HashMap<>();

View on GitHub (pinned to d6d39ce1c6)