flowable/flowable-engine · error · FlowableIllegalStateException
plan item instance is already suspended
Error message
plan item instance is already suspended
What it means
SuspendPlanItemInstanceOperation.isStateNotChanged detects idempotent re-suspension: if the plan item instance's old state already equals the new state (already suspended), suspending it again is illegal and a FlowableIllegalStateException is thrown instead of silently repeating the operation.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/SuspendPlanItemInstanceOperation.java:64
planItemInstanceEntity.setLastSuspendedTime(getCurrentTime(commandContext));
PlanItemDefinition planItemDefinition = planItemInstanceEntity.getPlanItem().getPlanItemDefinition();
if (planItemDefinition instanceof TimerEventListener) {
CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
List<TimerJobEntity> timerJobs = cmmnEngineConfiguration.getJobServiceConfiguration().getTimerJobEntityManager().findJobsByScopeIdAndSubScopeId(
planItemInstanceEntity.getCaseInstanceId(), planItemInstanceEntity.getId());
if (timerJobs != null && !timerJobs.isEmpty()) {
cmmnEngineConfiguration.getJobServiceConfiguration().getJobService().moveJobToSuspendedJob(timerJobs.get(0));
}
}
CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceSuspended(planItemInstanceEntity);
}
@Override
public boolean isStateNotChanged(String oldState, String newState) {
if (oldState != null && oldState.equals(newState)) {
throw new FlowableIllegalStateException("plan item instance is already suspended");
}
return false;
}
@Override
public boolean abortOperationIfNewStateEqualsOldState() {
return true;
}
@Override
public String getOperationName() {
return "[Suspend plan item]";
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Query the plan item instance and skip suspension when state is already 'suspended'.
- Make suspend actions idempotent at the application layer (guard with state check or lock).
- Catch FlowableIllegalStateException and treat it as success/no-op when appropriate.
- Avoid suspending a container whose children are partially suspended without checking states.
Example fix
// before
cmmnRuntimeService.suspendPlanItemInstance(planItemInstanceId);
// after
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
.planItemInstanceId(planItemInstanceId).singleResult();
if (pii != null && !"suspended".equals(pii.getState())) {
cmmnRuntimeService.suspendPlanItemInstance(planItemInstanceId);
} Defensive patterns
Strategy: validation
Validate before calling
// java
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
.planItemInstanceId(planItemInstanceId).singleResult();
if (pii == null || "suspended".equals(pii.getState())) return; // nothing to do Type guard
boolean needsSuspend(PlanItemInstance pii) {
return pii != null && !PlanItemInstanceState.SUSPENDED.equals(pii.getState());
} Try / catch
try {
cmmnRuntimeService.suspendPlanItemInstance(id);
} catch (FlowableIllegalStateException e) {
if (e.getMessage().contains("already suspended")) {
// idempotent no-op
}
} Prevention
- Guard suspend UI actions against double submission.
- Check child states before suspending partially suspended containers.
- Wrap suspend APIs in idempotent service methods.
When it happens
Trigger: Calling suspendPlanItemInstance (or suspending a case whose item is already suspended) for a plan item instance whose state is already 'suspended'.
Common situations: Double-invocation from retries or UI double-click; suspending a whole case tree where an item was already suspended in a prior partial suspension; concurrent suspend requests.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- plan item instance can only be resumed if the state is suspe
- Cannot exit stage with 'complete' event type as the stage '{
- Cannot exit case with 'complete' event type as the case '${c
- Can only trigger an event listener plan item that is in the
- Task ${taskId} is not suspended, so can't be activated
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/a063119c67a20375.
Report an issue: GitHub.