{"record":{"id":"547035dda64ff6e9","repo":"flowable/flowable-engine","slug":"cannot-set-suspension-state-for-already-in-st","errorCode":null,"errorMessage":"Cannot set suspension state '' for : already in state ''.","messagePattern":"Cannot set suspension state '' for : already in state ''\\.","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"warning","filePath":"modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/SuspensionStateUtil.java","lineNumber":41,"sourceCode":"import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;\nimport org.flowable.engine.impl.util.CommandContextUtil;\nimport org.flowable.task.api.history.HistoricTaskLogEntryType;\nimport org.flowable.task.service.TaskServiceConfiguration;\nimport org.flowable.task.service.impl.BaseHistoricTaskLogEntryBuilderImpl;\nimport org.flowable.task.service.impl.persistence.entity.TaskEntity;\n\nimport tools.jackson.databind.node.ObjectNode;\n\n/**\n * Helper class for suspension state\n * \n * @author Tijs Rademakers\n */\npublic class SuspensionStateUtil {\n\n    public static void setSuspensionState(ProcessDefinitionEntity processDefinitionEntity, SuspensionState state) {\n        if (processDefinitionEntity.getSuspensionState() == state.getStateCode()) {\n            throw new FlowableException(\"Cannot set suspension state '\" + state + \"' for \" + processDefinitionEntity + \"': already in state '\" + state + \"'.\");\n        }\n        processDefinitionEntity.setSuspensionState(state.getStateCode());\n        dispatchStateChangeEvent(processDefinitionEntity, state);\n    }\n\n    public static void setSuspensionState(ExecutionEntity executionEntity, SuspensionState state) {\n        if (executionEntity.getSuspensionState() == state.getStateCode()) {\n            throw new FlowableException(\"Cannot set suspension state '\" + state + \"' for \" + executionEntity + \"': already in state '\" + state + \"'.\");\n        }\n        executionEntity.setSuspensionState(state.getStateCode());\n        dispatchStateChangeEvent(executionEntity, state);\n    }\n\n    public static void setSuspensionState(TaskEntity taskEntity, SuspensionState state) {\n        if (taskEntity.getSuspensionState() == state.getStateCode()) {\n            throw new FlowableException(\"Cannot set suspension state '\" + state + \"' for \" + taskEntity + \"': already in state '\" + state + \"'.\");\n        }\n        taskEntity.setSuspensionState(state.getStateCode());","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/SuspensionStateUtil.java#L23-L59","documentation":"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.","triggerScenarios":"Calling RuntimeService.suspendProcessDefinitionByKey/ById (or activate*) on a definition that is already in the target state; the check compares getSuspensionState() with state.getStateCode().","commonSituations":"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.","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."],"exampleFix":"// before\nrepositoryService.suspendProcessDefinitionByKey(\"orderProcess\");\n// after\nboolean active = repositoryService.createProcessDefinitionQuery().processDefinitionKey(\"orderProcess\").active().count() > 0;\nif (active) {\n    repositoryService.suspendProcessDefinitionByKey(\"orderProcess\");\n}","handlingStrategy":"validation","validationCode":"boolean active = repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().count() > 0;\nboolean suspended = repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).suspended().count() > 0;\n// suspend only if active, activate only if suspended","typeGuard":null,"tryCatchPattern":"try { repositoryService.suspendProcessDefinitionByKey(key); } catch (FlowableException e) { if (e.getMessage().contains(\"already in state\")) { /* idempotent no-op */ } else throw e; }","preventionTips":["Check current suspension state before suspend/activate calls","Treat suspend/activate as non-idempotent operations","Serialize definition state changes to one owner/job"],"tags":["suspension","state-transition","process-definition"],"backgroundTag":"invalid-state-transition","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}