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

  1. Check that the global acquire lock configuration (globalAcquireLockEnabled) is consistent and the lock table/mechanism is shared by ALL executor nodes
  2. Verify only the intended number of async executors run against the same database schema
  3. 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
  4. 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

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


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