flowable/flowable-engine · warning · FlowableException
Cannot set suspension state '' for : already in state ''.
Error message
Cannot set suspension state '' for : already in state ''.
What it means
SuspensionStateUtil.setSuspensionState(ProcessDefinitionEntity, SuspensionState) throws when the process definition's current suspension state code already equals the requested state. Suspending an already-suspended definition (or activating an already-active one) is treated as an invalid state transition, so Flowable refuses rather than performing a no-op.
Solutions
- Check current state first via RepositoryService.createProcessDefinitionQuery().processDefinitionKey(key).suspensionState(...) and only call suspend/activate when it differs.
- Wrap the suspend/activate call in try-catch for FlowableException and treat the 'already in state' case as success/idempotent.
- Serialize state changes (e.g. single scheduler owner or locking) to avoid duplicate suspend commands.
Example fix
// before
repositoryService.suspendProcessDefinitionByKey("orderProcess");
// after
boolean active = repositoryService.createProcessDefinitionQuery().processDefinitionKey("orderProcess").active().count() > 0;
if (active) {
repositoryService.suspendProcessDefinitionByKey("orderProcess");
} Defensive patterns
Strategy: validation
Validate before calling
boolean active = repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().count() > 0; boolean suspended = repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).suspended().count() > 0; // suspend only if active, activate only if suspended
Try / catch
try { repositoryService.suspendProcessDefinitionByKey(key); } catch (FlowableException e) { if (e.getMessage().contains("already in state")) { /* idempotent no-op */ } else throw e; } Prevention
- Check current suspension state before suspend/activate calls
- Treat suspend/activate as non-idempotent operations
- Serialize definition state changes to one owner/job
When it happens
Trigger: Calling RuntimeService.suspendProcessDefinitionByKey/ById (or activate*) on a definition that is already in the target state; the check compares getSuspensionState() with state.getStateCode().
Common situations: Admin UI double-clicking a suspend button; scheduled job suspending definitions without checking current state; retry logic re-issuing a suspend command after a partial failure; cluster nodes racing to suspend the same definition.
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
- Cannot start process instance. Process definition
- no deployed process definition found with id
- Can only disable a plan item instance which is in state…
- Can only enable a plan item instance which is in state…
- Can only trigger an event listener plan item that is in the…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/547035dda64ff6e9.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/SuspensionStateUtil.java:41
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.task.api.history.HistoricTaskLogEntryType;
import org.flowable.task.service.TaskServiceConfiguration;
import org.flowable.task.service.impl.BaseHistoricTaskLogEntryBuilderImpl;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import tools.jackson.databind.node.ObjectNode;
/**
* Helper class for suspension state
*
* @author Tijs Rademakers
*/
public class SuspensionStateUtil {
public static void setSuspensionState(ProcessDefinitionEntity processDefinitionEntity, SuspensionState state) {
if (processDefinitionEntity.getSuspensionState() == state.getStateCode()) {
throw new FlowableException("Cannot set suspension state '" + state + "' for " + processDefinitionEntity + "': already in state '" + state + "'.");
}
processDefinitionEntity.setSuspensionState(state.getStateCode());
dispatchStateChangeEvent(processDefinitionEntity, state);
}
public static void setSuspensionState(ExecutionEntity executionEntity, SuspensionState state) {
if (executionEntity.getSuspensionState() == state.getStateCode()) {
throw new FlowableException("Cannot set suspension state '" + state + "' for " + executionEntity + "': already in state '" + state + "'.");
}
executionEntity.setSuspensionState(state.getStateCode());
dispatchStateChangeEvent(executionEntity, state);
}
public static void setSuspensionState(TaskEntity taskEntity, SuspensionState state) {
if (taskEntity.getSuspensionState() == state.getStateCode()) {
throw new FlowableException("Cannot set suspension state '" + state + "' for " + taskEntity + "': already in state '" + state + "'.");
}
taskEntity.setSuspensionState(state.getStateCode());View on GitHub (pinned to d6d39ce1c6)