apache/dolphinscheduler · error · UnsupportedOperationException
updateWorkflowInstance + workflowInstanceId + state failed,
Error message
updateWorkflowInstance + workflowInstanceId + state failed, expect original state is + originalStatus.name() + actual state is : {} + workflowInstance.getState().name() What it means
updateWorkflowInstanceState uses optimistic-style state transition: the UPDATE only succeeds when the row's current state equals the expected originalStatus. If update count != 1 but the instance exists, its stored state differs from what the caller expected, and UnsupportedOperationException is thrown with both expected and actual states.
Source
Thrown at dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/repository/impl/WorkflowInstanceDaoImpl.java:76
public void upsertWorkflowInstance(@NonNull WorkflowInstance workflowInstance) {
if (workflowInstance.getId() != null) {
updateById(workflowInstance);
} else {
insert(workflowInstance);
}
}
@Override
public void updateWorkflowInstanceState(Integer workflowInstanceId, WorkflowExecutionStatus originalStatus,
WorkflowExecutionStatus targetStatus) {
int update = mybatisMapper.updateWorkflowInstanceState(workflowInstanceId, originalStatus, targetStatus);
if (update != 1) {
WorkflowInstance workflowInstance = mybatisMapper.selectById(workflowInstanceId);
if (workflowInstance == null) {
throw new UnsupportedOperationException("updateWorkflowInstance " + workflowInstanceId
+ " state failed, the workflow instance is not exist in db");
}
throw new UnsupportedOperationException(
"updateWorkflowInstance " + workflowInstanceId + " state failed, expect original state is "
+ originalStatus.name() + " actual state is : {} " + workflowInstance.getState().name());
}
}
@Override
public void forceUpdateWorkflowInstanceState(Integer id, WorkflowExecutionStatus status) {
mybatisMapper.forceUpdateWorkflowInstanceState(id, status);
}
/**
* find last scheduler process instance in the date interval
*
* @param workflowDefinitionCode definitionCode
* @param taskDefinitionCode definitionCode
* @param dateInterval dateInterval
* @return process instance
*/View on GitHub (pinned to 02eac45a1b)
Solutions
- Re-read the instance's current state and recompute the transition from the actual state.
- Serialize state changes (single actor per instance) or tolerate already-transitioned states instead of throwing.
- Retry the whole read-modify-write cycle when a concurrent transition is detected.
- Log expected vs actual state and treat it as a benign conflict where the business logic allows.
Example fix
// before
workflowInstanceDao.updateWorkflowInstanceState(id, WorkflowExecutionStatus.RUNNING, WorkflowExecutionStatus.STOP);
// after
WorkflowInstance wi = workflowInstanceDao.queryById(id);
if (wi != null && wi.getState() == WorkflowExecutionStatus.RUNNING) {
workflowInstanceDao.updateWorkflowInstanceState(id, wi.getState(), WorkflowExecutionStatus.STOP);
} else {
log.warn("instance {} already in state {}, skip stop", id, wi == null ? null : wi.getState());
} Defensive patterns
Strategy: retry
Validate before calling
WorkflowInstance wi = workflowInstanceDao.queryById(id);
if (wi == null || wi.getState() != expectedOriginal) {
log.warn("instance {} state is {}, not {}; skip transition", id, wi == null ? null : wi.getState(), expectedOriginal);
return;
} Try / catch
try {
workflowInstanceDao.updateWorkflowInstanceState(id, original, target);
} catch (UnsupportedOperationException e) {
// re-read state and retry the transition from actual state
WorkflowInstance wi = workflowInstanceDao.queryById(id);
if (wi != null) {
workflowInstanceDao.updateWorkflowInstanceState(id, wi.getState(), target);
}
} Prevention
- Always base transitions on a freshly read state, not cached status.
- Avoid concurrent actors issuing transitions for the same instance.
- Make kill/restart idempotent: tolerate the instance already being in the target state.
- Log expected vs actual state to diagnose race conditions.
When it happens
Trigger: Two threads/processes (e.g. master failover plus a kill command, or a retry) change the instance state concurrently so the expected originalStatus no longer matches; calling with a wrong originalStatus for the instance's current phase.
Common situations: Master/worker race conditions during failover; submitting state transitions twice (double kill/restart); code assuming READY_PAUSE/RUNNING when the instance already moved to STOP/FAILURE.
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
- {joined failure messages, e.g. Failed do action <executeType
- DELETE_ENVIRONMENT_ERROR
- DELETE_SCHEDULE_BY_ID_ERROR
- 10165
- 10092
AI-assisted analysis of apache/dolphinscheduler@02eac45a1b (2026-09-06).
Data as JSON: /api/errors/e9dbd94bce85052a.
Report an issue: GitHub.