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
This is a WARN-level log emitted by the async executor's job acquisition thread when a FlowableOptimisticLockingException occurs while acquiring due async jobs and the global acquire lock is enabled. Two executors/threads raced on the same job row; the optimistic-lock check in the database rejected the update. With a global acquire lock this should be rare, so it is logged as a warning rather than expected-cluster noise.
Source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/AcquireAsyncJobsDueRunnable.java:207
LOGGER.debug("Jobs acquired: {}, rejected: {}, for engine {}", acquiredJobs.size(), rejectedJobs.size(), getEngineName());
if (rejectedJobs.size() > 0) {
lifecycleListener.rejectedJobs(getEngineName(), rejectedJobs.size(), acquiredJobs.size(), asyncExecutor.getMaxAsyncJobsDuePerAcquisition());
// some jobs were rejected, so the queue was full; wait until attempting to acquire more.
return asyncExecutor.getDefaultQueueSizeFullWaitTimeInMillis();
}
if (acquiredJobs.size() >= asyncExecutor.getMaxAsyncJobsDuePerAcquisition()) {
return 0L; // the maximum amount of jobs were acquired, so we can expect more.
}
} catch (FlowableOptimisticLockingException optimisticLockingException) {
lifecycleListener.optimistLockingException(getEngineName(), asyncExecutor.getMaxAsyncJobsDuePerAcquisition());
if (globalAcquireLockEnabled) {
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());
}
} catch (Throwable e) {
LOGGER.warn("exception for engine {} during async job acquisition: {}", getEngineName(), e.getMessage(), e);
}
return asyncExecutor.getDefaultAsyncJobAcquireWaitTimeInMillis();
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Check that the global acquire lock configuration (globalAcquireLockEnabled) is consistent and the lock table/mechanism is shared by ALL executor nodes
- Verify only the intended number of async executors run against the same database schema
- If clusters are expected and the lock is not in use, accept the exception as normal (it is logged at debug) and tune acquire wait time
- Retry the acquisition cycle; the exception is transient and the job will be picked up by the winning thread
Example fix
// before: multiple nodes with divergent config <property name="asyncExecutorActivate" value="true"/> <!-- globalAcquireLock not enabled --> // after: enable and share the global acquire lock across nodes <property name="asyncExecutor" ref="asyncExecutor"/> // in code: asyncExecutor.setGlobalAcquireLockEnabled(true); // all nodes use the same lock store
Defensive patterns
Strategy: retry
Validate before calling
// before starting executors, ensure identical config on all nodes
if (nodes.stream().anyMatch(n -> !n.asyncExecutor.isGlobalAcquireLockEnabled())) {
throw new IllegalStateException("globalAcquireLockEnabled must match on all nodes");
} Try / catch
try {
executor.acquireAndExecute();
} catch (FlowableOptimisticLockingException e) {
log.debug("job already acquired by another node, retrying next cycle", e);
} Prevention
- Use identical async executor config on every node
- Enable and correctly share the global acquire lock store
- Tune maxAsyncJobsDuePerAcquisition and wait times for cluster size
- Accept rare occurrences as benign; monitor frequency
When it happens
Trigger: Multiple async executor instances (or threads) acquiring jobs against the same job tables while globalAcquireLockEnabled=true; a job row is updated concurrently between select and update during acquireAndExecuteJobs.
Common situations: Clustered Flowable deployments sharing one database; misconfigured global lock (e.g. lock manager table missing or shared incorrectly); multiple engines pointing at the same job service tables without proper locking config.
Related errors
- Could not lock process instance
- exception for engine {} during async job acquisition: {}
- exception during timer job acquisition for engine {}. Except
- Optimistic locking exception (using global acquire lock) for
- Could not lock process instance
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/4a9576cfc87a09d2.
Report an issue: GitHub.