flowable/flowable-engine · error · FlowableException

Tenant mismatch between Case Instance ('${caseInstance.getTe

Error message

Tenant mismatch between Case Instance ('${caseInstance.getTenantId()}') and Case Definition ('${destinationTenantId}') to migrate to

What it means

When changing plan item state (live case instance migration), Flowable validates the case instance's tenant against the destination case definition's tenant. On mismatch, it only allows the operation if fallback-to-default-tenant is enabled and the default tenant provider yields the destination tenant; otherwise this FlowableException is thrown.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java:393

        if (migrationCallbacks != null && !migrationCallbacks.isEmpty()) {
            CaseDefinition sourceCaseDefinition = CommandContextUtil.getCaseDefinitionEntityManager(commandContext).findById(originalCaseDefinitionId);
            for (CaseInstanceMigrationCallback caseInstanceMigrationCallback : migrationCallbacks) {
                caseInstanceMigrationCallback.historicCaseInstanceMigrated(historicCaseInstance, sourceCaseDefinition, caseDefinitionToMigrateTo, document);
            }
        }
    }

    protected ChangePlanItemStateBuilderImpl prepareChangeStateBuilder(CaseInstance caseInstance, CaseDefinition caseDefinitionToMigrateTo, 
            CaseInstanceMigrationDocument document, CommandContext commandContext) {
        
        String destinationTenantId = caseDefinitionToMigrateTo.getTenantId();
        if (!Objects.equals(caseInstance.getTenantId(), destinationTenantId)) {
            
            CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
            if (cmmnEngineConfiguration.isFallbackToDefaultTenant() && cmmnEngineConfiguration.getDefaultTenantProvider() != null) {
                
                if (!Objects.equals(destinationTenantId, cmmnEngineConfiguration.getDefaultTenantProvider().getDefaultTenant(caseInstance.getTenantId(), ScopeTypes.CMMN, caseDefinitionToMigrateTo.getKey()))) {
                    throw new FlowableException("Tenant mismatch between Case Instance ('" + caseInstance.getTenantId() + "') and Case Definition ('" + destinationTenantId + "') to migrate to");
                }
            
            } else {
                throw new FlowableException("Tenant mismatch between Case Instance ('" + caseInstance.getTenantId() + "') and Case Definition ('" + destinationTenantId + "') to migrate to");
            }
        }

        String caseInstanceId = caseInstance.getId();
        ChangePlanItemStateBuilderImpl changePlanItemStateBuilder = new ChangePlanItemStateBuilderImpl();
        changePlanItemStateBuilder.caseInstanceId(caseInstanceId);

        List<String> mappedPlanItemDefinitionIds = new ArrayList<>();
        for (ActivatePlanItemDefinitionMapping planItemDefinitionMapping : document.getActivatePlanItemDefinitionMappings()) {
            mappedPlanItemDefinitionIds.add(planItemDefinitionMapping.getPlanItemDefinitionId());
            changePlanItemStateBuilder.activatePlanItemDefinition(planItemDefinitionMapping);
        }
        
        for (TerminatePlanItemDefinitionMapping planItemDefinitionMapping : document.getTerminatePlanItemDefinitionMappings()) {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Pass a destination case definition with the same tenant id as the running case instance.
  2. Enable fallbackToDefaultTenant on CmmnEngineConfiguration and provide a DefaultTenantProvider resolving to the destination tenant.
  3. Redeploy the target case definition under the case instance's tenant.
  4. Verify the case instance tenant via the runtime service and align the change-state request.

Example fix

// before
caseService.createChangePlanItemStateBuilder()
    .movePlanItemIdTo("pi1", "newStage"); // target def tenant 'b', instance tenant 'a'
// after
cmmnEngineConfiguration.setFallbackToDefaultTenant(true);
cmmnEngineConfiguration.setDefaultTenantProvider(...); // resolve 'a' -> 'b' or align tenants
Defensive patterns

Strategy: validation

Validate before calling

if (!Objects.equals(caseInstance.getTenantId(), destinationDefinition.getTenantId())) {
    // align or ensure fallback config before invoking changePlanItemState
}

Try / catch

try { builder.changeState(); } catch (FlowableException e) { if (e.getMessage().contains("Tenant mismatch")) { /* align tenants or configure provider */ } throw e; }

Prevention

When it happens

Trigger: Calling CaseService/ChangePlanItemStateBuilder with a case definition whose tenantId differs from caseInstance.getTenantId(), while cmmnEngineConfiguration.isFallbackToDefaultTenant() is false or no DefaultTenantProvider is configured.

Common situations: Multi-tenant deployments where a case definition was re-deployed under another tenant; programmatic plan item state changes after tenant re-organization; migration tooling that omits the tenant id.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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