flowable/flowable-engine · error · FlowableIllegalStateException
plan item instance can only be resumed if the state is suspe
Error message
plan item instance can only be resumed if the state is suspended
What it means
ResumePlanItemInstanceOperation.isStateNotChanged asserts that a plan item instance is only resumed from the SUSPENDED state. If the recorded old state is non-null and anything other than SUSPENDED, resuming is an illegal transition and a FlowableIllegalStateException is thrown.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/ResumePlanItemInstanceOperation.java:66
protected void internalExecute() {
planItemInstanceEntity.setLastAvailableTime(getCurrentTime(commandContext));
PlanItemDefinition planItemDefinition = planItemInstanceEntity.getPlanItem().getPlanItemDefinition();
if (planItemDefinition instanceof TimerEventListener) {
CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext);
List<SuspendedJobEntity> suspendedJobs = cmmnEngineConfiguration.getJobServiceConfiguration().getSuspendedJobEntityManager().findJobsBySubScopeId(planItemInstanceEntity.getId());
if (suspendedJobs != null && !suspendedJobs.isEmpty()) {
cmmnEngineConfiguration.getJobServiceConfiguration().getJobService().activateSuspendedJob(suspendedJobs.get(0));
}
}
CommandContextUtil.getCmmnHistoryManager(commandContext).recordPlanItemInstanceAvailable(planItemInstanceEntity);
}
@Override
public boolean isStateNotChanged(String oldState, String newState) {
if (oldState != null && !PlanItemInstanceState.SUSPENDED.equals(oldState)) {
throw new FlowableIllegalStateException("plan item instance can only be resumed if the state is suspended");
}
return oldState != null && oldState.equals(newState) && abortOperationIfNewStateEqualsOldState();
}
@Override
public boolean abortOperationIfNewStateEqualsOldState() {
return true;
}
@Override
public String getOperationName() {
return null; // Default one is ok.
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Check planItemInstance.getState() equals 'suspended' before resuming.
- If resuming a case, note that only suspended children are affected and avoid resuming non-suspended items individually.
- Serialize suspension/resume operations so concurrent agenda operations cannot change state in between.
- Catch FlowableIllegalStateException and treat it as a no-op if the item is already active.
Example fix
// before
cmmnRuntimeService.resumePlanItemInstance(planItemInstanceId);
// after
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
.planItemInstanceId(planItemInstanceId).singleResult();
if (pii != null && "suspended".equals(pii.getState())) {
cmmnRuntimeService.resumePlanItemInstance(planItemInstanceId);
} Defensive patterns
Strategy: validation
Validate before calling
// java
PlanItemInstance pii = cmmnRuntimeService.createPlanItemInstanceQuery()
.planItemInstanceId(planItemInstanceId).singleResult();
if (pii == null || !"suspended".equals(pii.getState()))
throw new IllegalStateException("Plan item not suspended: " + planItemInstanceId); Type guard
boolean isSuspended(PlanItemInstance pii) {
return pii != null && PlanItemInstanceState.SUSPENDED.equals(pii.getState());
} Try / catch
try {
cmmnRuntimeService.resumePlanItemInstance(id);
} catch (FlowableIllegalStateException e) {
if (e.getMessage().contains("can only be resumed if the state is suspended")) {
// already resumed or wrong state: treat as no-op
}
} Prevention
- Always read current state before resume calls.
- Serialize suspend/resume operations per plan item.
- Handle resume of already-resumed items as idempotent no-ops in your service layer.
When it happens
Trigger: Triggering a resume operation (directly or via plan item / case resume APIs) on a plan item instance whose current state is e.g. AVAILABLE, ACTIVE, COMPLETED, or EXITED instead of SUSPENDED.
Common situations: Resuming the whole case when only some plan items were suspended; resuming an already-resumed plan item; concurrent operations changed the state before resume ran.
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 is already suspended
- 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/62d85fb1c0dc4892.
Report an issue: GitHub.