flowable/flowable-engine · warning · FlowableException

Cannot delete

Error message

Cannot delete 

What it means

After finding the job, DeleteJobCmd rejects deletion when the job is locked (lockOwner != null), i.e. the async executor's acquisition thread already picked it up and is executing it. Flowable throws a plain FlowableException("Cannot delete ... when the job is being executed. Try again later.") to prevent deleting a job mid-execution.

Source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/DeleteJobCmd.java:92

    protected JobEntity getJobToDelete(CommandContext commandContext) {
        if (jobId == null) {
            throw new FlowableIllegalArgumentException("jobId is null");
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Deleting job {}", jobId);
        }

        JobEntity job = jobServiceConfiguration.getJobEntityManager().findById(jobId);
        if (job == null) {
            throw new FlowableObjectNotFoundException("No job found with id '" + jobId + "'", 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

  1. Retry after a short delay — the message explicitly says 'Try again later'; the lock expires if the executor dies or the job finishes.
  2. Stop or scale down the async executor (or pause job acquisition) before bulk job deletion/maintenance.
  3. Check job.getLockOwner()/lock expiration time before deleting, and skip locked jobs.
  4. Shut down the engine gracefully (executor shutdown waits for running jobs) in tests/scripts that delete jobs.

Example fix

// before
managementService.deleteJob(jobId); // may be locked by the async executor
// after
Job job = managementService.createJobQuery().jobId(jobId).singleResult();
if (job != null && job.getLockOwner() == null) {
    managementService.deleteJob(jobId);
}
Defensive patterns

Strategy: retry

Validate before calling

Job job = managementService.createJobQuery().jobId(jobId).singleResult();
if (job != null && job.getLockOwner() != null) {
    LOGGER.info("Job {} is locked by {} — retry later", jobId, job.getLockOwner());
    return;
}

Try / catch

int attempts = 0;
while (attempts < 3) {
    try {
        managementService.deleteJob(jobId);
        break;
    } catch (FlowableException e) {
        if (!e.getMessage().contains("being executed") || ++attempts == 3) throw e;
        Thread.sleep(2000);
    }
}

Prevention

When it happens

Trigger: Calling managementService.deleteJob / DeleteJobCmd on a job that a job executor node has acquired (ACT_RU_JOB.LOCK_OWNER_ set, lock expiry not yet passed) — typical in clustered setups or immediately after an async step became runnable.

Common situations: Deleting an async continuation job while a service task is executing on another node; cleanup scripts running while the async executor is active; long-running jobs whose lock is repeatedly renewed so retries keep colliding.

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/85b694d5074fc93f. Report an issue: GitHub.