flowable/flowable-engine · warning · FlowableException
Cannot delete ${job} when the job is being executed. Try aga
Error message
Cannot delete ${job} when the job is being executed. Try again later. What it means
After finding the timer job, DeleteTimerJobCmd checks job.getLockOwner(); a non-null lock owner means the async executor acquisition thread already locked the job for execution, so deleting it would race with a running job. The command throws a plain FlowableException telling the caller to retry later.
Source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/DeleteTimerJobCmd.java:83
protected TimerJobEntity getJobToDelete(CommandContext commandContext) {
if (timerJobId == null) {
throw new FlowableIllegalArgumentException("jobId is null");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Deleting job {}", timerJobId);
}
TimerJobEntity job = jobServiceConfiguration.getTimerJobEntityManager().findById(timerJobId);
if (job == null) {
throw new FlowableObjectNotFoundException("No timer job found with id '" + timerJobId + "'", Job.class);
}
// We need to check if the job was locked, ie acquired by the job acquisition thread
// This happens if the job was already acquired, but not yet executed.
// In that case, we can't allow to delete the job.
if (job.getLockOwner() != null) {
throw new FlowableException("Cannot delete " + job + " when the job is being executed. Try again later.");
}
return job;
}
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Retry the delete after a short delay until the lock owner is cleared (job executed or lock released).
- Cancel the owning execution/process instance instead of deleting the timer directly, so the engine cleans up safely.
- Catch FlowableException and implement exponential backoff for this transient condition.
- Temporarily suspend the job executor during bulk timer cleanup operations.
Example fix
// before
managementService.deleteTimerJob(timerJobId); // FlowableException if locked
// after
TimerJobInfo job = managementService.createTimerJobQuery().timerJobId(timerJobId).singleResult();
if (job != null) {
managementService.deleteTimerJob(timerJobId); // retry with backoff on FlowableException
} Defensive patterns
Strategy: retry
Validate before calling
TimerJobInfo j = managementService.createTimerJobQuery().timerJobId(id).singleResult(); boolean locked = j != null; // lock owner not exposed; rely on retry for FlowableException
Try / catch
try { managementService.deleteTimerJob(id); } catch (FlowableException e) { scheduler.schedule(this::deleteWithBackoff, 2, SECONDS); } Prevention
- Implement exponential backoff retries around timer job deletion.
- Prefer canceling the owning execution/process instance over deleting locked jobs manually.
- Suspend the job executor during bulk timer maintenance windows.
When it happens
Trigger: managementService.deleteTimerJob(id) called while the async executor has acquired but not yet executed the timer job (lock owner column populated).
Common situations: Admin/UI code deleting timers at the same moment the job executor fires them; high-load systems where the acquisition thread grabs timers between a user query and the delete; canceling a process instance manually via job deletion instead of letting engine cascade handle it.
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
- Could not find a scope execution for compensation boundary e
- {workerId} does not hold a lock on the requested job
- Task '' is already claimed by someone else.
- Sequential ad-hoc sub process in ${execution} already has an
- Process instance '" + processInstanceId + "' is already clai
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/57c8c77370f78af4.
Report an issue: GitHub.