Developer track · 7 min read
Flow vs subflow in MuleSoft: error handling, processing strategy, and when to reach for each
By the MulePrep team · Updated June 2026
The choice of flow vs subflow in MuleSoft looks like a styling preference and is actually a behavioral one. Picking a subflow over a flow changes how errors are handled and which processing strategy your logic runs on - two things that decide whether an application is correct, not just tidy. This guide builds the distinction from how Mule 4 actually executes each construct, extends it to private flows, and ends with the routing decision people conflate with it: a Choice router versus Scatter-Gather. For either developer certification, this is bread-and-butter material the exam probes constantly.
The mental model to carry: a flow is a self-contained unit that can be triggered and can handle its own failures, while a subflow is a reusable fragment that runs inline inside its caller and pushes failures back up to it. Everything else follows from that.
What a flow is in Mule 4
A flow is the top-level container in a Mule application. Three things define it:
- It can have an event source. A flow may start with a source such as an
HTTP Listener, aScheduler, or a connector listener that receives a message. A flow with a source is reachable from outside the application - that source is its trigger. - It has its own error-handling section. Every flow can declare an error handler with
On Error ContinueandOn Error Propagatescopes. This is the flow's own boundary for catching and classifying failures. (For the difference between those two scopes, see On Error Continue vs On Error Propagate.) - It runs on its own processing strategy. In Mule 4 the runtime owns concurrency through a non-blocking, reactive engine. A flow is a scheduling boundary: the runtime decides which thread pool runs it and can switch threads at IO points - which is why a flow can behave asynchronously relative to whatever referenced it.
A private flow is just a flow with no event source. It cannot be triggered from outside; the only way to reach it is a flow-ref from another flow. Crucially, it keeps the other two properties of a flow: its own error handling and its own processing strategy.
What a subflow is, and how it differs
A subflow is a deliberately stripped-down container built for one purpose: reusable, inline logic. It differs from a flow on exactly the points above.
- No event source. A subflow can never be a trigger. Like a private flow, it is only reachable by
flow-ref. - No error-handling section. This is the load-bearing difference. A subflow cannot declare an error handler. Any error raised inside it propagates straight up to the error handler of the flow that called it.
- No processing strategy of its own. A subflow always inherits the processing strategy of its caller and executes inline - think of it as the runtime splicing the subflow's processors directly into the calling flow at the point of the reference. It runs synchronously within that flow, on the same thread context.
That inline, synchronous-to-the-caller behavior is the whole point. A subflow is the Mule equivalent of extracting code into a private helper method: same call stack, errors bubble to the caller, no scheduling boundary introduced.
Flow vs subflow vs private flow: the decision table
These three constructs - flow, private flow, subflow - form a clean spectrum. The two questions that separate them are: does this reusable logic need its own error handling? and does it need its own processing behavior?
| Construct | Event source? | Own error handler? | Processing strategy | Reach it via |
|---|---|---|---|---|
| Flow | Yes | Yes | Its own | Its source (trigger) |
| Private flow | No | Yes | Its own | flow-ref only |
| Subflow | No | No | Inherits caller's | flow-ref only |
Read the table as a decision:
- Does the logic need a trigger (HTTP, schedule, queue listener)? It must be a flow with a source.
- Is it reusable logic with no trigger, but it needs its own error handling or asynchronous behavior? Use a private flow. It can catch its own errors and can run on a different processing strategy than its caller.
- Is it reusable logic that should run inline and let any error fail back to the caller? Use a subflow. It is the lightest option and keeps the call synchronous.
A common mistake is defaulting to subflows everywhere "because they are simpler", then being surprised that an error inside one cannot be contained locally. If you find yourself wanting an On Error Continue inside a subflow, that is the signal to promote it to a private flow.
Calling them: flow-ref and why the target type matters
All three are invoked the same way - the Flow Reference component (flow-ref) - so the call site looks identical regardless of target. What differs is the behavior you get, and that is determined entirely by what you are pointing at.
flow-refto a subflow: the processors run inline, synchronously, on the caller's thread context; errors propagate to the caller's handler. No new scheduling boundary.flow-refto a private flow: control transfers to a flow that has its own error boundary and its own processing strategy. Errors can be caught there before they ever reach the caller, and the runtime may schedule it differently.
So flow-ref is not "call a subflow" - it is "transfer to a referenced flow or subflow", and the reference's target type is what changes the error and concurrency semantics. This is exactly the kind of distinction the MuleSoft Certified Developer Level 1 exam tests, and the consequences (where errors surface, what runs on which thread) deepen at Developer Level 2, where production resilience is the theme. The jump between those two exams is mapped in MCD Level 1 to Level 2.
Choice router vs Scatter-Gather: the routing decision
People often pose "flow vs subflow" alongside "which router do I use", because both shape how a message moves through an app. But routers answer a different question - not how do I package reusable logic, but which path(s) should this message take. The two routers that get conflated are Choice and Scatter-Gather.
- Choice router is conditional, single-path routing. It evaluates conditions in order and executes the first matching branch (with an optional default), exactly like an
if / else-if / else. Only one branch runs. Use it when the message should follow exactly one of several mutually exclusive paths - for example, route by customer tier or by payload type. - Scatter-Gather is parallel, all-paths routing. It sends the same message to every branch concurrently, then waits and aggregates the results into a single collection keyed by route. Use it when you need results from multiple independent calls at once - for example, enriching an order by calling inventory, pricing, and shipping services in parallel.
| Choice router | Scatter-Gather | |
|---|---|---|
| Branches that run | Exactly one (first match) | All, concurrently |
| Purpose | Conditional routing | Parallel fan-out + join |
| Result | The chosen branch's output | Aggregated results from all branches |
| Error behavior | Error from the one branch | Collects branch errors into a composite routing error |
The trap is reaching for Scatter-Gather when you only need one branch (you pay for concurrency you do not use and a more complex error path), or reaching for Choice when you actually need every call to happen (you serialize work that could run in parallel). Pick by intent: one of these is Choice; all of these at once is Scatter-Gather.
Worth noting: Scatter-Gather's concurrency is routing-level parallelism, distinct from whether the overall integration is synchronous or asynchronous. If the deeper question is whether the caller should wait at all, that is a different decision - see Sync vs async integration.
Putting it together
Flow, private flow, and subflow are three points on a spectrum of how much a reusable unit owns. A subflow owns nothing but its processors; it runs inline and fails back to its caller. A private flow owns its error handling and processing strategy but still has no trigger. A flow owns all of that plus an event source. Choose by what the logic genuinely needs, not by which icon is fewer clicks away.
Choice and Scatter-Gather sit beside that decision as the routing answer: one path or every path. Get both distinctions right and your applications behave the way the diagram promises - which is also how you avoid the classic exam traps. Want to see whether you can spot the right construct under exam conditions? The free 10-question demo is built from questions exactly like these.
Frequently asked questions
- What is the difference between a flow and a subflow in MuleSoft?
- A flow can have an event source (like an HTTP Listener) and its own error-handling section, and it always runs on its own processing strategy. A subflow has no source and no error handler, and it inherits the processing strategy of whatever called it, executing inline like a synchronous code block. Use a flow when you need a trigger or local error handling; use a subflow for reusable, synchronous logic that should fail back to its caller.
- What is a private flow and how is it different from a subflow?
- A private flow is a flow with no event source, so it cannot be triggered externally and is only reachable by flow-ref. Unlike a subflow, a private flow can have its own error-handling section and can run asynchronously. Reach for a private flow when reusable logic needs its own try/catch or a different processing behavior than its caller; reach for a subflow when it should run inline and let errors propagate to the parent.
- Does a subflow have its own error handling?
- No. A subflow cannot declare an error-handling section. Any error raised inside a subflow propagates up to the error handler of the flow that invoked it. If the reusable logic must catch and recover from its own errors, use a private flow instead, which can declare On Error Continue or On Error Propagate.
- When should I use a Choice router versus Scatter-Gather?
- Use a Choice router when exactly one of several branches should run based on a condition - it is an if/else-if for routes. Use Scatter-Gather when every branch should run concurrently and you need all their results combined - it is a parallel fan-out and join. Choice picks one path; Scatter-Gather runs all paths at once and aggregates them.
- Can a subflow run asynchronously?
- Not on its own. A subflow always inherits the processing strategy of its caller and executes inline, so it runs synchronously within the calling flow. To decouple work, call a private flow that you configure for asynchronous execution, or hand the work off through an Async scope or a VM queue, which is the asynchronous-integration pattern.
Independent study resource - not affiliated with, endorsed by, or connected to MuleSoft or Salesforce; their trademarks belong to their owners. All practice questions are original.