flowable/flowable-engine · warning

Optimistic locking exception (using global acquire lock) for

Error message

Optimistic locking exception (using global acquire lock) for engine {}

What it means

WARN log emitted by logOptimisticLockingException when a FlowableOptimisticLockingException occurs during timer job acquisition or move while the global acquire lock is enabled. It indicates two threads raced to modify the same timer job entity despite the global lock, so the DB optimistic-lock version check rejected one of them.

Source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/AcquireTimerJobsRunnable.java:244

                commandExecutor.execute(new BulkMoveTimerJobsToExecutableJobsCmd(jobManager, timerJobs));
            } else {
                commandExecutor.execute(new MoveTimerJobsToExecutableJobsCmd(jobManager, timerJobs));
            }

        } catch (FlowableOptimisticLockingException optimisticLockingException) {
            logOptimisticLockingException(optimisticLockingException);
            unlockTimerJobs(timerJobs); // jobs have been acquired before, so need to unlock when exception happens here

        } catch (Throwable t) {
            LOGGER.warn("exception during timer job move for engine {}. Exception message: {}", getEngineName(), t.getMessage(), t);
            unlockTimerJobs(timerJobs); // jobs have been acquired before, so need to unlock when exception happens here

        }
    }

    protected void logOptimisticLockingException(FlowableOptimisticLockingException optimisticLockingException) {
        if (configuration.isGlobalAcquireLockEnabled()) {
            LOGGER.warn("Optimistic locking exception (using global acquire lock) for engine {}", getEngineName(), optimisticLockingException);

        } else {
            LOGGER.debug(
                "Optimistic locking exception during async job acquisition. If you have multiple async executors running against the same database, " +
                    "this exception means that this thread tried to acquire a due async job, which already was acquired by another " +
                    "async executor acquisition thread.This is expected behavior in a clustered environment. " +
                    "You can ignore this message if you indeed have multiple async executor acquisition threads running against the same database. " +
                    "For engine {}. Exception message: {}",
                getEngineName(), optimisticLockingException.getMessage());

        }
    }

    protected void sleep(long millisToWait) {
        if (millisToWait > 0) {
            try {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("timer job acquisition thread for engine {} sleeping for {} millis", getEngineName(), millisToWait);

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Ensure globalAcquireLockEnabled is identically configured on every node
  2. Verify the lock is actually held for the whole acquire+move cycle (check custom lock manager TTL vs cycle duration)
  3. Ignore if rare — the runnable logs it and unlocks affected jobs, and the job is retried
  4. Check for clock skew between nodes that could shorten effective lock TTL

Example fix

// before: asymmetric config
nodeA: asyncExecutor.setGlobalAcquireLockEnabled(true);
nodeB: asyncExecutor.setGlobalAcquireLockEnabled(false);
// after: identical on all nodes
asyncExecutor.setGlobalAcquireLockEnabled(true);
Defensive patterns

Strategy: retry

Validate before calling

if (nodes.stream().map(n -> n.getGlobalAcquireLockEnabled()).distinct().count() > 1) throw new IllegalStateException("inconsistent global lock config");

Try / catch

try {
    moveJobs();
} catch (FlowableOptimisticLockingException e) {
    unlockTimerJobs(timerJobs);
    // transient: job will be retried next cycle
}

Prevention

When it happens

Trigger: Concurrent update of the same timer job row during acquisition or the move-to-executable phase with globalAcquireLockEnabled=true; stale entity versions from a previous failed cycle.

Common situations: Clustered nodes with inconsistently enabled global locks; long-running move operations overlapping the next acquire cycle; lock owner mismatches after node restarts.

Related errors


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