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
- Ensure globalAcquireLockEnabled is identically configured on every node
- Verify the lock is actually held for the whole acquire+move cycle (check custom lock manager TTL vs cycle duration)
- Ignore if rare — the runnable logs it and unlocks affected jobs, and the job is retried
- 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
- Keep globalAcquireLockEnabled uniform across the cluster
- Ensure lock TTL exceeds acquire+move cycle duration
- Synchronize clocks across nodes
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
- Optimistic locking exception (using global acquire lock) for
- Error while waiting for global acquire lock for engine {}
- Could not lock process instance
- exception during timer job acquisition for engine {}. Except
- exception during timer job move for engine {}. Exception mes
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b3b7ea3ceba2a2ae.
Report an issue: GitHub.