Why Flowable exclusive jobs matter for parallel process instances
Exclusive jobs are the setting that keeps Flowable from running two jobs for the same process instance at the same time. By default, jobs created from one process instance are executed sequentially, one after another, so the engine does not have two threads updating the same execution and variable rows at once. At a parallel gateway join, that single-writer behavior is what prevents the optimistic locking failures teams often see when two branches finish within moments of each other and both try to advance the same instance.
The risk shows up when the sequencing assumption no longer holds. A team that runs parallel branches or async continuations may see FlowableOptimisticLockingException errors that appear only under load, pass in test, and retry successfully most of the time. Those symptoms are a reason to check how jobs for each instance are created and executed before changing the database or the thread pool. Confirm in your own model which activities are marked async, where parallel branches meet, and whether any external API calls update the same instance while the engine is working on it.
The Flowable advanced documentation describes the async executor as a thread pool that runs timers and asynchronous jobs, and says it coordinates multiple nodes through lock owners and lock expiration times stored in the database. That design matters when you run more than one engine node against the same database. An expired lock is recovered and the job can be picked up by another node, so a job that is still running on a slow node can overlap with a retry on another node if lock durations are shorter than the actual work. Review your lock settings and job durations together, and confirm the exact exclusive flag behavior for your Flowable version in the Flowable advanced docs before you change it. Teams need to understand this setting before they enable non-exclusive jobs to gain throughput. Non-exclusive execution removes the sequencing guarantee, so the design must accept concurrent updates to the same instance and handle the resulting conflicts in the model or the application. Plan a load test that uses your real branch counts, async continuations and API callers, and record every optimistic locking exception and lock expiry. Those results decide whether the default is enough or whether the model needs changes.
Exclusive or non-exclusive: choosing the job mode for each async step
Parallel joins are the most common place where exclusive jobs matter. When two or more async branches reach a parallel gateway, each branch tries to update the same execution tree and the same instance state. With exclusive jobs, the engine runs those continuations one at a time for that process instance, so the second branch waits instead of writing over the first.
- What exclusive gives you: Joins complete in a predictable order, and the optimistic locking conflict at the join is avoided in the normal case.
- What non-exclusive trades away: Branches can run in parallel on the same instance, so one of them may fail with a
FlowableOptimisticLockingExceptionand retry. - Failure to test: Model a fork with several async tasks that join, then load the process with concurrent starts and check the job table for failed or retried jobs at the join.
- Retry behavior: Confirm how many retries your version performs by default and whether a failed join continuation leaves the instance in a state you can recover.
- Modeling option: A join that does little work may need no async step at all, which removes the conflict entirely.
Before you accept non-exclusive joins, ask your engineers to show the load test results and the failed job count for the same scenario in both modes.
Independent async continuations are branches that do not join soon and mostly touch their own variables or call external systems. Here the cost of serializing them is latency. Exclusive jobs make one branch wait for another on the same instance even when neither needs the other's data, so throughput per instance drops.
- What exclusive gives you: Simple reasoning about variable writes, because only one continuation for the instance is active at a time.
- What non-exclusive trades away: Parallel progress on one instance, with the risk that both branches write the process variables or the same execution at the same moment.
- Failure to test: Run branches that write a shared process variable and compare the results and retry counts with exclusive and non-exclusive jobs.
- Design check: Keep shared writes at the end of the branch or move them into a joined step, so that most work runs without contention.
- External calls: Long calls to other systems hold a job for a long time. Exclusive mode then blocks the rest of that instance's async work, so measure the wait before deciding.
The decision usually depends on whether the branches share state. Confirm that answer from the model, not from an assumption about speed.
Timers create their own jobs, and those jobs can fire at the same time as a user task completion, a message, or another async continuation on the same instance. The Flowable async executor acquires jobs from the database, uses lock owners and lock expiration times to coordinate nodes, and recovers jobs whose locks have expired. Exclusivity decides whether a timer job can run beside other work for the same instance.
- What exclusive gives you: A timer and a competing continuation on the same instance run in sequence, which avoids most races on boundary events.
- What non-exclusive trades away: A boundary timer can fire while a task is being completed, so one side may see a stale execution and fail.
- Failure to test: Set a short timer, complete the related task at the same moment in a load script, and check whether the boundary path or the task completion wins and whether the loser fails cleanly.
- Lock settings: Review the lock duration and the acquisition behavior in the advanced documentation before a timer-heavy load test, so that slow jobs are not reacquired by another node.
- Recovery: Confirm that an expired lock is picked up by another node and that the timer does not fire twice.
For timers, ask for test evidence showing the race outcome, because the correct answer depends on the event's modeling.
API callers are the case exclusive jobs do not control. A REST call or application code that completes a task, sets a variable, or signals a process runs in its own transaction, outside the job executor. If a job for the same instance is running at that moment, the API call and the job can update the same rows, and the exclusive setting on the job does not stop the API call from colliding with it.
- What exclusive gives you: Only ordering among jobs. It does not serialize API calls against jobs or against each other.
- What non-exclusive trades away: Nothing extra for API callers, but the collision risk exists in both modes, so the choice must not be made on the assumption that API calls are safe.
- Failure to test: Send a task completion through the REST application while an async continuation runs on the same instance, and record which request receives the optimistic locking error.
- Client handling: Have API clients retry on conflict and check the task state before retrying, so a completed task is not submitted twice.
- Concurrency rules: Decide which actor owns each instance at a given step, and document it for the application team.
Ask your team how each client path handles a locking error, because exclusive jobs cannot prevent that error for these callers.
Job configuration options and when each fits
This table compares the exclusive flag on async continuations, non-exclusive continuations, synchronous continuations and the async executor settings that set how many jobs run across nodes. Read each row for its effect on throughput, its locking risk and the test to run before you change a setting. Exclusive jobs are the safer default at joins, and the executor settings decide how much available capacity your jobs can use.
| Option | Throughput | Locking risk | Test to run |
|---|---|---|---|
| Exclusive async continuation (default) | Parallelism is capped per instance, but separate instances still run in parallel | Low at joins, because competing jobs for one instance wait their turn | Fire parallel branches into one join under load and check for zero optimistic locking errors |
| Non-exclusive async continuation | Independent branches of one instance can run at the same time | Higher, because jobs may update the same execution or variables together | Clear the flag, repeat the join test and count FlowableOptimisticLockingException occurrences |
| Synchronous continuation (no async) | No queue, but work runs in the caller's transaction and holds it longer | Conflicts surface to the API caller, so the caller must retry | Send concurrent API calls to one instance and confirm which caller receives the conflict |
| Async executor thread pool size | Sets how many jobs one node runs at once, so more threads raise database load | Indirect, since more concurrent jobs increase contention on hot rows | Ramp job volume in staging and watch queue depth, lock waits and database connections |
| Lock duration and expiration | Long locks slow recovery after a node fails, and short locks cause repeated acquisition | Expired locks let another node pick up a job that is still running | Stop a node mid-job in staging and confirm the job completes once after recovery |
| Number of nodes sharing job tables | Adds capacity, but each node adds acquisition queries against the same tables | Lock owners coordinate nodes, so misconfigured nodes compete for the same jobs | Add a node under load and compare throughput and any duplicate execution |
| Exact configuration property names | Confirm the keys and defaults for the flag and executor in your version's Configuration docs | Depends on the values you set for your version | Confirm names and defaults in the Configuration docs before any tuning change |
How to set exclusive jobs in a Flowable model and verify them
This sequence marks the async steps in a BPMN model, proves the exclusive behavior under parallel load, and confirms the result on a production-like database across two nodes. The output is a model and a set of evidence that the joins and job tables behave as intended.
Inventory the asynchronous steps in the model
The process designer and a Flowable engineer list every parallel branch, join, and wait state in the BPMN diagram, then mark which activities need an asynchronous continuation. Each parallel gateway that merges branches is recorded, since these are where optimistic locking conflicts usually appear. The step is done when every join and each async candidate has a named owner and a reason for being async or synchronous.
Set the job flags in the BPMN XML
The engineer adds the Flowable async and exclusive attributes to the chosen service tasks and gateways in the process XML, and keeps exclusive set to true on the branches that converge at a join. Each flag is checked against the Flowable BPMN extension documentation for the engine version in use. The step closes when a diff of the model shows the flags only on the intended elements and the file validates in the modeler.
Build a test database that matches production
The platform team provisions a test database using the same engine family as production, chosen from the supported types listed in the Flowable configuration docs (H2, MySQL, Oracle, PostgreSQL, DB2 and Microsoft SQL Server). Locking and transaction behavior differ between these databases, so an H2 test alone does not prove the production result. Confirm the database version and, for MySQL, that it supports millisecond precision as the docs require. The step is done when the connection string, versions and schema match the production record.
Deploy the model and start the engine
The team deploys the revised process definition to the test environment using the same deployment path as production, and confirms the async executor starts with the intended settings. The engineer checks that the deployment is recorded and that new process instances use the new version. The step is done when a test instance reaches the join and the async jobs appear in the job tables with the expected exclusive state.
Run a parallel-join load test
The QA team creates a load script that starts many instances of the same process, each with parallel branches that converge at the join, and runs it against the test environment. The script records failed starts, optimistic locking exceptions and the time jobs wait before running. The step is done when the test reports zero unexpected optimistic locking exceptions at the join for the agreed load profile, with the profile documented alongside the results.
Check the job tables for contention and failures
The database administrator and engineer query the async and dead-letter job tables during and after the load test, looking for jobs stuck on lock owners, expired locks that were not recovered, and failed jobs with exception messages. Any repeated failure is traced to its branch and fixed in the model or the delegate before the test is repeated. The step is done when the job tables show no unexplained failed jobs and lock expiry only where the test deliberately simulated a stopped node.
Confirm behavior across two engine nodes
The team runs two engine nodes against the same database and repeats the load test with jobs split between them. The engineer checks that each job is acquired by one node only, that a stopped node's expired locks are picked up by the other, and that the join still completes once. The step is done when a recorded run shows no duplicate execution of a job and the instance history reflects a single completion for each branch.
Record the evidence for the change
The team writes down the model version, database and engine versions, load profile, job table findings and the two-node result, and attaches the test logs. The change owner reviews this record before the model is promoted to production. The step is done when the record is stored with the change ticket and the reviewer has accepted it or listed the remaining risks with an owner for each.
Operational risks of changing job concurrency
Every change to job concurrency trades one kind of risk for another. Exclusive jobs protect data integrity at the cost of parallelism, and non-exclusive jobs recover throughput by moving the burden onto your delegates, transactions and monitoring. The cards below cover the risks that tend to surface after a change reaches production.
Throughput loss from serialization
Exclusive jobs make the jobs of one process instance run one after another, so a parallel branch that once finished in overlapping steps can now queue behind its siblings. The loss shows up as longer end-to-end instance duration, not as errors. Before changing anything, measure how many jobs per instance are actually waiting at the same time, and confirm that the slowest branch, not the average, drives the business deadline.
Lock contention in the job tables
The async executor coordinates nodes through lock owners and lock expiration times stored in the database, so every acquisition and release touches shared job rows. Raising the number of jobs that can run in parallel increases how often nodes compete for the same rows and how often locks are taken and released. Check database wait events and lock metrics under realistic peak load, and watch for deadlocks between acquisition queries and job completion writes.
Duplicate side effects in delegates
When non-exclusive jobs from one instance run in parallel, two threads can execute the same delegate logic against the same variables. A payment call, an email or a message publish inside a service task may then happen twice, and a retry after a timeout can repeat it again. Review each delegate for idempotency, using business keys or external reference IDs, before it is allowed to run concurrently.
Transaction boundary mistakes
Moving a job boundary, marking a task as asynchronous, or changing exclusivity alters where a transaction commits and rolls back. A delegate that calls an external system before a commit point can leave the outside world changed while the engine rolls back its own state. Map every changed boundary against the transaction design of the surrounding application, and test rollback paths explicitly rather than only the success path.
Monitoring gaps after the change
A change that reduces contention can hide problems: jobs may stop failing loudly while waiting longer, or duplicates may appear without an error. Dashboards built for the old pattern may no longer show the right signals. Confirm which metrics cover job queue depth, job age, retries and failed jobs, and set alerts on the job state your team will watch after the change, using the monitoring endpoints your Flowable version provides.
Audit and data protection review
Changing concurrency changes the order and number of recorded events, which can affect audit trails, history records and any personal data written by parallel steps. Have your compliance and privacy owners review the change before release, confirm which history level is in use, and check whether duplicate or reordered records need explanation. Treat this as a review gate, not a formality.
Questions teams ask before changing Flowable job concurrency
Are exclusive jobs the default in Flowable?
Yes. Flowable runs asynchronous jobs that belong to the same process instance one at a time by default, which is what exclusive jobs means. The engine uses this to stop two continuations of one instance from updating the same execution and variable rows at once. That protection matters most at parallel joins, where several branches finish close together. Most teams should keep the default until they have measured a throughput limit and understand what relaxing it changes for data consistency. The async executor material in the Flowable advanced docs explains how jobs are acquired and locked. Confirm the default for the engine version you run before you change any model.
How much effort does a Flowable concurrency review and load test take?
Effort depends on how many process models use async steps, how many parallel branches meet at joins, and how sharp your job volume peaks. A review usually starts with your process definitions and job configuration, then maps which instances write to shared records. A load test needs a staging environment with a database close to production, a representative job mix, and enough cycles to compare the default setting with any change. No supplied figure sets a duration for this work, so ask for a written breakdown of review time, test design, test runs and retesting. Ask which items are fixed and which depend on what the first review finds.
What drives the cost of a Flowable concurrency review?
The cost comes mostly from skilled time, so the main drivers are the number of process models, the number of async paths and joins, and how much of your test environment must be built. Costs split into one-time work, such as the review, test harness, tuning and handover, and recurring work, such as retesting after process changes or engine upgrades. Your internal costs include process owner and DBA time, which is easy to overlook. A partner is paid for the written scope, and your team pays for its own hours during the engagement. Ask any provider to price the review, the load test and the optional retest separately so you can see which items recur.
Can our internal team change Flowable job concurrency without a partner?
Often yes, if the team already knows the Flowable engine, the process models in scope and how your database handles locks. The work has three parts: finding which instances share records, changing the job settings in the models, and proving the result under load. Teams without a load testing habit or without a staging copy of production data tend to struggle with the proof step. Be realistic about the coverage you need: a change that affects only one model is a different job from a change that touches a shared pattern across dozens of processes. Decide who owns the change record, the rollback plan and the sign-off before you start.
What do we need from process owners, DBAs and a test environment?
You need three groups of people and one environment. Process owners must explain which branches run in parallel, which steps can safely run later, and what a failed step means for the business. DBAs must confirm how the engine's tables are indexed, how locks behave on your database platform, and what load the test can place on shared systems. Engineers must own the job configuration and the test scripts. The test environment should match production closely enough in database type, sizing and data volume to show lock contention. If you cannot supply a copy of production scale data, agree in writing how results will be interpreted before the test runs.
How does this choice affect a future Flowable upgrade?
Concurrency settings sit inside your process models and configuration, so they travel with every upgrade. Each major release can change the baseline your application runs on, so a load test that passed on one version should be repeated after a move to another. Check the release notes for the version you plan to adopt, such as the Flowable 8.0.0 release notes, for any changes to Java, Spring or Jakarta requirements that affect your test harness. Record the chosen job settings and the reason for each in your upgrade checklist so the next team does not have to rediscover them.
When is a non-exclusive job worth the risk?
A non-exclusive job is worth considering when a specific step is slow, independent of other branches, and proven to be safe to run alongside its siblings. Typical candidates are steps that only read data or write to records no other branch touches. The risk is that two jobs from one instance can now compete for the same rows, which can produce optimistic locking failures and retries. Before you switch, document the data each branch writes, test the join under peak load, and confirm how failed jobs are retried and monitored. If you cannot name the shared records with confidence, keep the job exclusive and address throughput with capacity or external workers instead.
What should we ask for in a quote for this work?
Ask for a written scope that lists the process models in scope, the review method, the load test design and the expected deliverables. Ask which items are one-time and which recur, such as retesting after model changes or after an engine upgrade. Request a separate price for any optional work, including tuning, retesting and handover training. Confirm what you must supply, including process owner time, DBA access, a staging environment and test data. Finally, ask how changes after sign-off are handled and how the rollback plan is documented. DeployOpen works from a written scope, so a quote follows from the items you agree on.
What a DeployOpen engagement on job concurrency covers
A DeployOpen engagement on job concurrency is a fixed-scope review of how your Flowable jobs are modeled, configured and tested, with the decisions written down at the end. The work starts with the process and case models that generate jobs: parallel branches, joins, timer boundary events, asynchronous continuations and the delegates or expressions that run inside them. For each, the review identifies which jobs are exclusive, which are not, and where a join or API caller could contend for the same process instance. It also checks the async executor settings in your Flowable configuration, such as the job acquisition and lock-related properties, against the behavior the models are expected to produce.
Next, the team runs load tests in your own infrastructure, against a database and cluster that resemble production. The tests reproduce the concurrency patterns found in the review, such as many parallel branches reaching a join or repeated calls updating the same instance, and record optimistic locking failures, job retries and lock contention. Where a non-exclusive job mode is under consideration, the tests show what it changes and what it risks in your data. Nothing in the test plan depends on DeployOpen hosting your systems, and results stay in your environment unless you decide otherwise. Ask the partner to confirm the load profile, test data handling and the environment used before work begins.
Scope, deliverables, timelines and any environment access are agreed in writing before work starts, and the written scope is the basis for the quote. DeployOpen does not publish a price list, so the cost depends on the number of process and case models in scope, the number of concurrency patterns to test, and how much tuning and retesting follows. The final documentation records each job mode decision, the reasons for it, the settings used and the test evidence, so your team can maintain the choice after the engagement ends. Ask for those deliverables by name when you request a quote.
How DeployOpen scopes and supports Flowable concurrency work
DeployOpen plans, implements, customizes, tests and supports self-hosted Flowable engines inside your own infrastructure. For exclusive jobs and concurrency, we start from your process models and job volumes, then agree a written scope before any build work begins.
How we scope
- Review which BPMN models have parallel branches, joins and asynchronous continuations that touch the same process instance.
- Record in the written scope which exclusive and non-exclusive job settings each process type will use, and why.
- Engagements are fixed-scope, so deliverables and timelines are agreed in writing before work starts; ongoing support is available as a retainer.
How we build
- Configure async executor and job settings in your Flowable Spring or Java configuration, with changes tracked in your version control.
- Model joins and parallel gateways so the engine does not contend for one instance's data, and test each pattern.
- Run concurrency tests that replay parallel job completions and record optimistic locking exceptions, retries and throughput.
How we hand over
- Deliver a written record of each job setting, the modeling rule behind it and the reason it was chosen.
- Document the failure signals to watch, such as rising failed jobs or locking exceptions, with the steps to investigate them.
- Walk your engineers through the test suite so they can rerun concurrency checks after each process change.
How we support
- Review job configuration and failed job trends when you upgrade Flowable or add a new process that uses joins.
- Confirm with your team which metrics and logs your monitoring stack will collect from the Flowable engine.
- Continuous testing and upgrade work can be added to a retainer if your process volume keeps growing.

