Flowable multi-instance tasks and parallel loops

Flowable multi-instance activities run one task or subprocess per item in a collection, either one after another or in parallel. This page explains how to configure them, where they break down at scale, and what to confirm before you build.

On this page

When a Flowable multi-instance loop fits your process

A multi-instance activity in BPMN runs one task or subprocess once for each item in a collection, such as one approval per customer, one notice per invoice or one check per line item. In Flowable, the activity takes its items from a collection variable and creates one instance per item. Each instance receives the current item in an element variable, which you name in the model, so the same task definition can handle every entry. Flowable supports sequential execution, where instances run one after another, and parallel execution, where all instances are created together and the process waits for them to finish. Confirm the exact attribute names and defaults for your version in the Flowable docs before you model against them.

The choice between sequential and parallel depends on what the items touch. Use sequential execution when each item depends on the result of the previous one, or when downstream systems cannot take concurrent calls. Use parallel execution when items are independent and throughput matters, but keep in mind that every instance adds execution state in the database and that many concurrent jobs compete for the async executor. A completion condition lets you end the loop early, for example when one reviewer rejects an item, so the design should state which outcome closes the loop and what happens to instances still open. Ask your Flowable team to confirm how completion conditions behave with boundary events for your version, since this area changes between releases.

Teams often reach for a loop inside a Java delegate because it is faster to write. That approach keeps the iteration in code, so the engine cannot show each item as its own activity, cannot attach a boundary timer or error to one item, and cannot resume from a single failed item without rerunning the whole delegate. A multi-instance activity keeps each item visible in the process history and lets you attach events to the activity. Before you commit, check the Flowable 8.0.0 release notes for changes that affect multi-instance behavior, and test a realistic collection size in a staging environment, since large collections raise the cost of each run and should be sized against your database and job capacity.

Configuration options and when each fits

This table compares the main multi-instance settings on a Flowable activity and the trade-off each one creates. Read each row as a decision: find the workload shape you have, then check the trade-off and the element to confirm in the Flowable BPMN docs before you model it.

OptionTypical fitMain trade-offElement or setting to confirm
Sequential multi-instanceItems that must be handled in order, such as steps that depend on the previous resultSlower overall throughput, since each item waits for the one before itSequential multi-instance marker on the activity; confirm the isSequential attribute in the Flowable BPMN docs
Parallel multi-instanceIndependent items that can be processed at the same time, such as notifying a list of recipientsHigher load on the async executor and database, and harder ordering of resultsParallel multi-instance marker; confirm the default behavior when isSequential is false
Collection inputYou already hold a list of items in a process variableCollection size is read when the activity starts, so large lists increase startup workflowable:collection or the collection element on the multi-instance loop characteristics
Cardinality inputA fixed count is known at design time or derived from a number variableLess flexible when item data must be passed, and it needs a separate variable for the countloopCardinality element on the multi-instance loop characteristics
Element and index variablesEach instance needs its own item or position for expressions and service callsVariable names can collide with process variables, and they hold data for every instanceflowable:elementVariable and flowable:elementIndexVariable; confirm naming in the Flowable docs
Completion conditionStop early once enough instances succeed, such as a quorum or first acceptable answerRemaining instances are cancelled, so their partial work and side effects need a plancompletionCondition expression on the multi-instance loop characteristics
Async flag on the activityEach instance should commit separately, which limits rollback scope for large batchesMore jobs in the async executor, so retries and lock contention become a sizing questionflowable:async on the activity; confirm how it interacts with multi-instance in the transaction docs
Boundary events on the activityTimeouts or interrupts that should apply to the whole loop, not each instanceBehavior differs between interrupting and non-interrupting events on the loopBoundary event attached to the multi-instance activity; confirm scope rules in the Flowable BPMN docs

How to set up a multi-instance activity in a Flowable project

This sequence takes a team from a collection of items to a tested multi-instance activity in a non-production Flowable environment. Each step ends with a check so that problems surface before the model reaches shared or production systems.

  1. Define the collection variable and its source

    Decide which process variable holds the list of items, where that list comes from (a service task, a form submission, a previous call activity or an external system), and what each element looks like. Set the variable type explicitly and confirm which activity writes it. Check that the variable exists in the runtime after the preceding step by inspecting the process instance variables through the Java or REST API, and confirm the list is not empty for a representative test case.

  2. Add the multi-instance marker to the activity

    In the BPMN model, attach the multi-instance marker to the user task, service task or embedded subprocess that should run once per item. Set the collection reference to the variable from the previous step and name the element variable that each iteration will see. Keep the element variable name distinct from any existing process variable. Verify the XML in the modeler by opening the saved file and confirming the multiInstanceLoopCharacteristics element points to the expected collection and element variable names.

  3. Choose sequential or parallel execution

    Set the loop to sequential when iterations must run one after another, for example when each step depends on the output of the previous item, and to parallel when items are independent. Document the choice with the business owner, because it changes ordering, throughput and how many tasks appear at once. Confirm the chosen mode in the deployed definition, then start a test instance with three or four items and check that the number of active child executions or tasks matches the mode you selected.

  4. Add a completion condition where the loop can end early

    If the activity should stop before every item is processed, such as when one approval is rejected or a threshold is reached, add a completion condition expressed against the loop's completed-instance counts or an output variable. Keep the expression simple and test its boundary cases, including the empty collection and the case where the condition is met on the first item. Run a small test collection where the condition is true at a known position and confirm the process leaves the activity at that point, with remaining iterations no longer active.

  5. Attach boundary events to the multi-instance activity

    Add any timer, error or message boundary events that should apply to the whole loop, and decide whether each one interrupts the activity. Be clear that a boundary event on a multi-instance activity applies to the activity as a whole, not to individual iterations. Test each boundary event with a controlled trigger, such as a short timer in the test environment, and confirm that all child executions are cleaned up and the process follows the boundary path. Check the active executions afterward to make sure no iterations remain.

  6. Verify variable scope and output collection

    Confirm where each variable lives. The element variable exists per iteration, while results written in each iteration reach the parent only if an output collection or mapping is configured. Run the test collection and read the output variable after the activity completes. Compare its length and contents with the input collection, and confirm that no iteration variable leaks into the parent scope unless you intended it. Record the expected scope for each variable so the team can review it.

  7. Deploy to a test environment and run the test collection

    Deploy the process definition to a non-production environment that uses the same database type and Flowable version as the target. Run a small collection, a larger one that approaches the expected production size, and an empty collection. Inspect runtime instances, job execution and history after each run. The step is complete when every test case reaches the expected end state, the output matches the input, and the team has recorded the observed behavior for the production sizing discussion.

Boundary events on multi-instance activities

An interrupting boundary event attached to a multi-instance activity ends the activity when it fires. When the event triggers, Flowable stops the remaining work, so instances that have not finished are cancelled along with the activity. Use this shape when the whole collection becomes irrelevant once the event occurs, such as a cancellation request that stops a batch of approvals or a shutdown signal for a fan-out of notifications.

  • Cancelled instances: Instances still running are removed from the process, and any work they had not committed is lost unless it was already persisted.
  • Completed instances stay complete: Results from instances that finished before the event remain in variables and history.
  • Cleanup path: Downstream steps that rely on partial results need an explicit route, because the activity exits without a normal completion.
  • Event scope: Confirm whether a message or signal boundary event on your model reaches every instance or only the parent activity.
  • Queued jobs: Jobs already queued for a cancelled instance may still exist, so check how your version handles them.

Confirm these behaviors with a test that cancels a batch midway, then inspect the runtime and history data before you rely on them.

How large collections change runtime cost

Very large collections make a multi-instance activity expensive in ways the diagram does not show. Each instance of a multi-instance task or subprocess creates its own execution rows, and the engine writes history for every instance start and completion. If each item also carries a payload in process variables, the collection and its per-instance values are persisted with the process instance, so the database absorbs writes and storage growth in proportion to the item count. Parallel instances make this worse, because each one can produce async jobs that the async executor must acquire, run and release. Those jobs compete for the same job tables and lock rows, and when many nodes poll for work at once, lock contention and lock expiry can slow the whole engine, not just the one loop. Check the history level you have configured, since full history on a large loop is often the largest single source of writes.

Design choices usually reduce this load more than tuning does. Splitting the work so that each item or batch runs as a call activity gives each child process its own, smaller set of rows and variables, and keeps the parent instance small. Batching several items per instance, so that one loop iteration handles a page of records, cuts the number of executions and jobs at the cost of a longer transaction per iteration. Keep the collection variable small by storing identifiers and fetching the detail inside the delegate. Confirm with your Flowable version and database vendor how the engine stores each collection variable and what the history tables record for multi-instance activities, because those details change between releases. The practical limit depends on your hardware, database, Flowable version, async executor settings and history configuration, so there is no safe number to adopt from another team. Build a load test with a realistic collection size, run it against the same database you will use in production, and measure job queue depth, lock waits and table growth before you commit to a design.

Security and operational risks of loops over business data

A multi-instance activity multiplies whatever a single task exposes: every item adds a variable, a history record, a job and an authorization decision. The cards below cover the risks that tend to surface in production and the checks to run before a loop handles regulated or sensitive records.

Sensitive collection variables in history

A collection variable holding customer records, account numbers or case notes is copied into the process variables for each instance and, depending on history settings, into history tables. Check which variables are set as transient or excluded from history, and confirm with your data protection owner how long history rows are kept and who can query them. Keep only identifiers in the collection and look up the record inside the delegate.

Expressions and scripts evaluated per item

Completion conditions, collection expressions and script tasks inside a loop run once per element, so a heavy or badly guarded expression can slow the whole activity and expose data through logs. Review every expression for null handling, for references to variables that may be missing, and for script languages that allow arbitrary code. Prefer a Java delegate with a reviewed class over inline scripts for anything that touches business data.

Authorization skipped per instance

The engine does not check whether the person completing a task in one instance is allowed to act on that item. When a loop assigns each instance to a candidate group, a user may reach records outside their scope through the task API. Confirm that the application checks ownership of the business object on every task completion and on every query that lists instances.

Stuck instances after partial failures

If one item in a parallel loop throws an unhandled error, the rest of the activity can remain open while the failed item waits for a retry or a manual decision. Define how a failed item is compensated, skipped or escalated before go-live, and decide whether the completion condition should tolerate a set number of failures. Test a failure in the middle of a large collection, not only the first item.

Failed async jobs and retries

Multi-instance work is often split into asynchronous jobs, and each failed job is retried according to the configured retry settings before it moves to the deadletter state. Repeated retries against an external system can duplicate side effects such as payments or notifications. Make each delegate idempotent, set retry counts deliberately, and set up a daily review of failed and deadletter jobs.

Monitoring through the actuator endpoint

The Spring Boot starters expose an actuator endpoint at /actuator/flowable that reports on running processes. Teams often leave it open on a management port or do not alert on the counts it shows, so a growing set of waiting instances goes unnoticed. Restrict the endpoint to operations networks, confirm what it reveals about process variables, and agree on alert thresholds for open instances and failed jobs.

Questions teams ask before building multi-instance loops

What drives the cost and effort of building a multi-instance loop in Flowable?

The main driver is how many behaviors sit inside the loop, not the loop syntax itself. A loop that calls one delegate over a known list is small work. Effort rises with the number of activities inside the loop, the completion condition logic, how results are collected back into process variables, boundary events attached to the loop, and the failure and retry rules for individual items. Recurring costs come from keeping the collection size and async settings tested as volumes grow. Ask any partner for a quote that breaks out design, delegate code, tests and load checks as separate lines, with the collection sizes each line assumes.

How long does a design review for a multi-instance model usually take?

The length depends on how many loops the model contains and how many of them touch external systems. A review that covers one loop with a clear collection source is short. A review that maps several nested loops, error paths and boundary events takes longer because each path needs a decision on what happens to partial results. No duration is set in advance. Ask for the review scope in writing, including which models and collection sources are covered, and whether the output is a reviewed model, a written decision record or both.

What does the client team need to provide for a loop engagement?

The client team needs to supply the business rule that defines each item, the source of the collection, the systems each iteration calls, and the rule for what counts as a completed loop. Engineers need access to a non-production Flowable environment that matches the production database type, plus sample data at realistic sizes. Someone who owns the business outcome should confirm how partial failures are handled. Without these inputs, the design usually stalls on questions about ordering, duplicate calls and whether a failed item should stop the loop.

Can an in-house team build multi-instance loops without a partner?

Yes, if the team already runs Flowable or another BPMN engine in production and has someone who understands the async executor and transaction boundaries. The Flowable documentation covers the BPMN syntax, collection variables and completion conditions in detail. The risks an in-house team most often underestimates are large collections, retry behavior after a node restart and the effect of parallel execution on shared external systems. A partner can add value as an independent reviewer of the design and load test plan, even if the team writes the code.

How does choosing sequential or parallel execution change rework later?

Sequential execution is easier to reason about when iterations depend on each other, call a rate-limited service or must preserve order. Parallel execution finishes sooner when items are independent, but it makes partial failures and duplicate side effects harder to undo. Changing the choice after go-live usually means rewriting the model, migrating running instances and retesting the external calls. Decide the mode during design, document which iterations are idempotent, and record the reason in the model documentation so the next change starts from the same assumptions.

How should teams test loops over large collections safely?

Start with a test that uses a small collection to prove the logic, then move to a staging run with a collection size close to the largest expected input. Watch job counts, database growth in the history tables, lock contention in the async executor and the behavior of downstream systems under the same concurrency. Include a test where a node stops in the middle of the loop and another node must pick up the remaining jobs. Confirm the exact numbers you need with the vendor documentation for your chosen Flowable version and database before setting limits.

What changes when a team upgrades Flowable to a new major version?

Major upgrades can change the Java, Spring and Jakarta baseline, remove components and change default libraries, so loop models must be re-verified rather than assumed to behave the same. Check the release notes for the target version, confirm the Spring Boot and JSON library versions your application uses, and run your loop test suite against the new engine before any production move. Running instances that span the upgrade need a planned migration path. Confirm the supported upgrade route for your current version with the vendor before scheduling work.

How DeployOpen scopes and delivers multi-instance work

DeployOpen plans and builds Flowable multi-instance tasks and parallel loops inside your own Java stack, starting from the collection sizes, completion rules and data your processes must handle. Engagements are fixed-scope, so the written scope fixes what gets modeled, tested and handed over before build work begins.

How we scope

  • Inventory every process where a task or subprocess must run once per item, with expected collection sizes and peak volumes.
  • Agree in writing whether iterations run sequentially or in parallel, and which completion condition ends the loop.
  • Define the collection variables, element variables and output mappings each iteration reads and writes.
  • Confirm boundary event behavior on multi-instance activities and the performance limits you expect for large collections.

How we build

  • Model multi-instance activities in BPMN 2.0 with the Flowable extensions your engine version supports, confirmed against its documentation.
  • Write Java delegates or expressions that read per-item variables, keeping iteration logic idempotent under retries.
  • Test completion conditions, empty collections and single-item edge cases with unit tests against the Flowable engine.
  • Load-test large collections on a staging environment that matches your database and async executor settings.

How we hand over

  • Deliver the process definitions, delegate code, configuration and test suite in your version control system.
  • Document collection sizing limits, async executor settings and the variables each iteration depends on.
  • Walk your engineers through failed-job handling, retries and how to inspect running iterations in production.
  • Record the open decisions and the assumptions behind the chosen loop design for your architecture review.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer after handover.
  • Retainer work can cover re-testing multi-instance models when you upgrade Flowable or change database engines.
  • Review new loop designs against the scope agreed at the start, with changes handled through a written update.
  • Confirm support terms, response expectations and included testing with DeployOpen before the retainer starts.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.