flowable/flowable-engine · error · FlowableException
Programmatic error: unsupported transition ${transition} for
Error message
Programmatic error: unsupported transition ${transition} for ${planItemInstanceEntity} What it means
AsyncLeaveActivePlanItemInstanceJobHandler replays a plan item transition (stored in the job configuration) asynchronously. It only supports a fixed set of transition values (e.g. complete, terminate, fail, exit); anything else reaches the else branch and throws. This is a programming/configuration error: the serialized transition string in the job is not one the handler knows how to plan an operation for.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/AsyncLeaveActivePlanItemInstanceJobHandler.java:82
CommandContextUtil.getAgenda(commandContext).planExitPlanItemInstanceOperation(planItemInstanceEntity, exitCriterionId, exitType, exitEventType);
} else if (PlanItemTransition.TERMINATE.equals(transition)) {
String exitType = jsonConfiguration.path(OperationSerializationMetadata.FIELD_EXIT_TYPE).stringValue(null);
String exitEventType = jsonConfiguration.path(OperationSerializationMetadata.FIELD_EXIT_EVENT_TYPE).stringValue(null);
CommandContextUtil.getAgenda(commandContext).planTerminatePlanItemInstanceOperation(planItemInstanceEntity, exitType, exitEventType);
} else if (PlanItemTransition.FAULT.equals(transition)) {
String errorCode = jsonConfiguration.path(OperationSerializationMetadata.FIELD_ERROR_CODE).stringValue(null);
if (errorCode != null) {
String errorMessage = jsonConfiguration.path(OperationSerializationMetadata.FIELD_ERROR_MESSAGE).stringValue(null);
CmmnFault reconstructedError = errorMessage != null ? new CmmnFault(errorCode, errorMessage) : new CmmnFault(errorCode);
CommandContextUtil.getAgenda(commandContext).planFailPlanItemInstanceOperation(planItemInstanceEntity, reconstructedError);
} else {
CommandContextUtil.getAgenda(commandContext).planFailPlanItemInstanceOperation(planItemInstanceEntity);
}
} else {
throw new FlowableException("Programmatic error: unsupported transition " + transition + " for " + planItemInstanceEntity);
}
} catch (Exception e) {
throw new FlowableException("Could not deserialize job configuration", e);
}
} else {
throw new FlowableException("Invalid usage of " + TYPE + " job handler, variable scope is of type " + variableScope.getClass());
}
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Inspect the job's configuration (ACT_RU_JOB CONFIG_ column) and confirm the serialized transition name matches one supported by this handler version.
- Upgrade or align the Flowable version of the engine writing the job with the engine executing it, so both agree on transition names.
- If you produce these jobs with custom code, only pass supported transition values (complete, terminate, fail, exit).
- Check the deserialization code path for corruption — the surrounding catch wraps deserialization in 'Could not deserialize job configuration', so a corrupted config may masquerade as an unsupported transition.
Example fix
// before cmmnRuntimeService.completePlanItemInstance(planItemInstanceId); // wrong API writing custom transition // after // use only supported transition triggers that schedule async leave operations PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery().planItemInstanceId(id).singleResult(); cmmnRuntimeService.completePlanItemInstance(pii.getId());
Defensive patterns
Strategy: validation
Validate before calling
Set<String> supported = Set.of("complete","terminate","fail","exit");
if (!supported.contains(transition)) throw new IllegalArgumentException("Unsupported transition: " + transition); Try / catch
try {
// trigger plan item transition via runtime API
} catch (FlowableException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Programmatic error: unsupported transition")) {
// inspect job config / upgrade engine, do not blind-retry
throw new ConfigurationException("Invalid async leave transition", e);
}
throw e;
} Prevention
- Only use supported public runtime APIs (complete/terminate/fail plan item) to trigger async leave jobs
- Keep all Flowable nodes on the same version so transition names match
- Never hand-edit job configuration columns
- Add a test asserting the job config serialization round-trips
When it happens
Trigger: execute(job, caseInstanceId, variableScope, commandContext) deserializes the job configuration into a transition string and it is not one of the supported values (complete/terminate/fail/exit); the planFail branch is only entered for failure conditions, and all other unrecognized values fall through to the throw.
Common situations: Job configuration was hand-edited or produced by a different Flowable version writing transition names this build does not understand; a custom command serialized an arbitrary transition into the job configuration; deserialization yields an unexpected value after an upgrade.
Related errors
- Can only trigger a plan item that is in the ACTIVE state
- Plan item instance id is null
- Cannot find plan item instance for id ${planItemInstanceId}
- Can only disable a plan item instance which is in state ENAB
- Can only enable a plan item instance which is in state AVAIL
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/3051e23259407d7e.
Report an issue: GitHub.