7.1 For Each Scope: Payload Iteration, Batch Size & Streaming Data
Key Takeaways
- The For Each scope (<foreach>) processes collections sequentially on the calling thread, executing child message processors once per item (or chunk of items if batchSize > 1).
- Upon exiting the For Each scope, the original root payload that existed prior to entering the scope is automatically restored, but any flow variables modified or created inside the scope persist with their accumulated state.
- The scope provides a built-in, 1-indexed vars.counter tracking the current iteration index, and allows custom collection extraction via the collection attribute (defaulting to #[payload]).
- If an unhandled exception occurs inside a For Each iteration, iteration immediately aborts, remaining items in the collection are discarded, and execution routes to the enclosing flow's error handler.
- Unlike Parallel For Each (which splits execution across multiple threads and returns an aggregated result) or Batch Job (which processes asynchronously in persistent queues), For Each is synchronous, sequential, and lightweight.
For Each Scope: Payload Iteration, Batch Size & Streaming Data
In enterprise application integration, processing collections of records—such as arrays of purchase orders, lists of customer database rows, or parsed CSV lines—is a daily requirement. Mule 4 provides multiple mechanisms to iterate over and transform collections. The For Each scope (<foreach>) is the foundational, lightweight component for sequential collection processing within a synchronous flow execution context.
Understanding how <foreach> splits payloads, manages variable state, handles runtime exceptions, and preserves the original message payload is essential for developing reliable integration flows and passing the Salesforce Certified MuleSoft Developer I exam.
1. For Each Scope Architecture & Execution Model
The For Each scope splits any iterable collection (Java List, Array, Map, ResultSet, or streaming CursorProvider) into individual elements and executes its child message processors sequentially for each item.
+-----------------------------------------------------------------------------------------+
| FOR EACH SCOPE EXECUTION MODEL |
| |
| Inbound Payload: [ "Order-A", "Order-B", "Order-C" ] |
| Inbound Variables: { totalOrders: 0, processedCount: 0 } |
| | |
| v |
| +---------------------------------------------------------------------------------+ |
| | <foreach> Scope (Sequential Iteration on Calling Thread) | |
| | | |
| | Iteration 1: | |
| | - payload = "Order-A" | |
| | - vars.counter = 1 | |
| | - vars.processedCount updated to 1 | |
| | | |
| | Iteration 2: | |
| | - payload = "Order-B" | |
| | - vars.counter = 2 | |
| | - vars.processedCount updated to 2 | |
| | | |
| | Iteration 3: | |
| | - payload = "Order-C" | |
| | - vars.counter = 3 | |
| | - vars.processedCount updated to 3 | |
| +---------------------------------------------------------------------------------+ |
| | |
| v |
| Outbound Payload: [ "Order-A", "Order-B", "Order-C" ] <--- RESTORED ORIGINAL! |
| Outbound Variables: { totalOrders: 0, processedCount: 3 } <--- MODIFICATIONS PERSIST! |
+-----------------------------------------------------------------------------------------+
Key Architectural Principles:
- Single-Threaded & Synchronous: The For Each scope executes on the calling thread. It does not spawn background worker threads. Iteration
N+1never begins until iterationNhas fully completed all message processors inside the scope. - Payload Splitting: During each iteration, the
payloadis temporarily replaced with the single element being processed (or an array of elements ifbatchSize > 1). - Root Payload Restoration: When all iterations finish successfully, the Mule runtime discards the output of the final iteration and restores the original inbound payload that existed prior to entering the
<foreach>scope. - Variable Mutation Persistence: Any flow variables (
vars) created or modified inside the<foreach>scope persist across iterations and remain in the Mule event after exiting the scope.
2. Core Configuration Attributes
The <foreach> scope XML element provides four key attributes that control iteration behavior:
| Attribute | Type | Default Value | Description |
|---|---|---|---|
collection | DataWeave Expression | #[payload] | Expression defining the collection to iterate over. Can target nested collections (e.g., #[payload.orders]). |
batchSize | Integer | 1 | Number of records grouped together in each iteration. When set to N > 1, payload inside the scope is a sub-array of N items. |
rootMessageVariableName | String | rootMessage | Variable name storing the original root Mule message during iteration. Accessible inside the scope via vars.rootMessage. |
counterVariableName | String | counter | Name of the variable tracking iteration count. Accessible inside the scope as vars.counter (1-indexed). |
XML Blueprint: Basic Iteration with Custom Collection
<flow name="process-invoices-flow">
<http:listener config-ref="HTTP_Listener_config" path="/invoices" doc:name="Listener" />
<!-- Inbound payload is a JSON Object: { "batchId": "B-101", "items": [ {...}, {...} ] } -->
<set-variable variableName="totalProcessed" value="#[0]" doc:name="Init Counter" />
<set-variable variableName="failedIds" value="#[[]]" doc:name="Init Failed Array" />
<!-- Iterate specifically over payload.items -->
<foreach collection="#[payload.items]" counterVariableName="idx" rootMessageVariableName="originalOrder" doc:name="For Each Invoice">
<!-- Inside scope: payload is a single item from items array -->
<logger level="INFO"
message="#['Processing item ' ++ vars.idx ++ ' of batch ' ++ vars.originalOrder.payload.batchId ++ ' with ID: ' ++ payload.invoiceId]" />
<!-- Outbound HTTP Call to Billing API -->
<http:request method="POST" config-ref="Billing_HTTP_Config" path="/process">
<http:body><![CDATA[#[payload]]]></http:body>
</http:request>
<!-- Accumulate variable -->
<set-variable variableName="totalProcessed" value="#[vars.totalProcessed + 1]" doc:name="Increment" />
</foreach>
<!-- AFTER FOR EACH: -->
<!-- payload is restored to: { "batchId": "B-101", "items": [ ... ] } -->
<!-- vars.totalProcessed contains the final count -->
<logger level="INFO" message="#['Completed processing. Total items: ' ++ vars.totalProcessed]" />
</flow>
3. The batchSize Attribute (Chunking Records)
By default, batchSize="1", meaning child message processors execute once per individual element. However, when interfacing with external bulk endpoints (such as database bulk inserts or batch REST endpoints), processing records in chunks of 50, 100, or 500 is far more performant than individual network round-trips.
When batchSize is set to N > 1:
- The runtime groups the source collection into sub-arrays of up to
Nelements. - Inside the
<foreach>scope,payloadis anArraycontaining up toNitems. vars.counterincrements by 1 for each chunk/sub-array, not for each individual item.- The final chunk may contain fewer than
Nitems if the collection length is not evenly divisible bybatchSize.
+-----------------------------------------------------------------------------------------+
| BATCH SIZE CHUNKING (batchSize = 2) |
| |
| Inbound Collection: [ "A", "B", "C", "D", "E" ] (5 items) |
| |
| Iteration 1 (vars.counter = 1): payload = [ "A", "B" ] (size = 2) |
| Iteration 2 (vars.counter = 2): payload = [ "C", "D" ] (size = 2) |
| Iteration 3 (vars.counter = 3): payload = [ "E" ] (size = 1) |
| |
| Total Iterations: 3 |
+-----------------------------------------------------------------------------------------+
XML Configuration: Chunked Database Bulk Insert
<foreach collection="#[payload]" batchSize="100" doc:name="For Each Chunk of 100">
<!-- payload is an Array of up to 100 records -->
<db:bulk-insert config-ref="Database_Config" doc:name="Bulk Insert Chunk">
<db:sql><![CDATA[
INSERT INTO staging_accounts (account_id, name, status)
VALUES (:accountId, :name, :status)
]]></db:sql>
</db:bulk-insert>
<logger level="INFO" message="#['Inserted chunk #' ++ vars.counter ++ ' with records: ' ++ sizeOf(payload)]" />
</foreach>
4. Error Handling in For Each: Sequential Fail-Fast Semantics
A critical concept tested on the certification exam is how runtime errors behave inside <foreach>.
Default Behavior: Immediate Halt (Fail-Fast)
If an unhandled exception occurs during any iteration:
- The
<foreach>scope immediately halts execution. - All remaining, unprocessed items in the collection are discarded and never executed.
- The exception is routed directly to the enclosing flow's error handler (or global error handler).
- Any variable mutations completed during earlier successful iterations are retained in the flow context.
+-----------------------------------------------------------------------------------------+
| FOR EACH UNHANDLED ERROR PROPAGATION |
| |
| Collection: [ Item 1, Item 2, Item 3 (FAILS), Item 4, Item 5 ] |
| |
| - Item 1: Processed Successfully ---> vars.count = 1 |
| - Item 2: Processed Successfully ---> vars.count = 2 |
| - Item 3: Throws HTTP:CONNECTIVITY ERROR! |
| - Item 4: [SKIPPED / NEVER EXECUTED] |
| - Item 5: [SKIPPED / NEVER EXECUTED] |
| |
| Flow routes to <error-handler>: vars.count remains 2, error = HTTP:CONNECTIVITY |
+-----------------------------------------------------------------------------------------+
Resilient Pattern: Per-Item Error Isolation via <try> Scope
To allow iteration to continue even if individual records fail, wrap the inner processors in a Try scope with an On-Error Continue handler:
<foreach collection="#[payload]" doc:name="For Each Resilient">
<try doc:name="Try Record Processing">
<http:request method="POST" config-ref="CRM_HTTP_Config" path="/sync">
<http:body><![CDATA[#[payload]]]></http:body>
</http:request>
<set-variable variableName="successCount" value="#[vars.successCount + 1]" doc:name="Increment Success" />
<error-handler>
<!-- On-Error Continue catches failure, logs it, and allows For Each to proceed to next item -->
<on-error-continue enableNotifications="true" logException="true" doc:name="Catch and Continue">
<logger level="ERROR" message="#['Failed to sync record: ' ++ payload.id ++ ' Reason: ' ++ error.description]" />
<set-variable variableName="failureList" value="#[vars.failureList + payload.id]" doc:name="Add to Failure List" />
</on-error-continue>
</error-handler>
</try>
</foreach>
5. Streaming Data & Non-Repeatable Streams in For Each
When iterating over large datasets streamed from a Database (<db:select>) or File read operation, Mule 4 uses streaming cursor providers (CursorProvider).
- Repeatable Streams (Default): Mule 4 caches consumed parts of the stream in memory (or temporary disk buffers if exceeding buffer thresholds). When
<foreach>exits, the original stream can be re-read because the runtime manages cursor positions. - Non-Repeatable Streams: If non-repeatable streaming is configured, consuming the stream inside
<foreach>drains the stream iterator. While<foreach>still restores the root payload reference, attempting to read that non-repeatable stream after<foreach>will result in aSTREAM_MAXIMUM_SIZE_EXCEEDEDor empty stream error.
6. Architectural Comparison: For Each vs. Parallel For Each vs. Batch Job
MuleSoft provides three distinct collection processing constructs. Knowing when to use each is a high-frequency exam topic:
| Feature | For Each (<foreach>) | Parallel For Each (<parallel-foreach>) | Batch Job (<batch:job>) |
|---|---|---|---|
| Execution Threading | Single calling thread (Sequential) | Multiple worker threads (Concurrent / Parallel) | Dedicated thread pool across multiple steps (Asynchronous) |
| Payload After Scope | Restored to original inbound payload | Aggregated array of iteration output messages | BatchJobResult summary object (in On Complete) |
| Variable Scope | Modifications persist across iterations and outside scope | Modifications are isolated to each thread and lost upon exit | Modifications become Record Variables; lost outside batch job |
| Error Handling | Aborts immediately on first unhandled error | Processes all routes, then throws MULE:COMPOSITE_ROUTING | Configurable tolerance (maxFailedRecords, acceptPolicy) |
| Handoff Mode | Synchronous (blocks calling flow) | Synchronous (blocks until all parallel routes join) | Asynchronous (immediate handoff to batch instance) |
| Queue Persistence | None (in-memory only) | None (in-memory only) | Persistent Disk Queues (resilient across restarts) |
| Ideal Use Case | Small/medium collections requiring ordered execution or variable accumulation | Medium collections where independent I/O calls can run in parallel | Massive volume (millions of records), ETL, bulk loading, multi-step staging |
7. Exam Watch: Core For Each Scenarios
[!IMPORTANT] Payload vs. Variable Rule Always remember: Payload is RESTORED; Variables PERSIST. If a flow transforms
payloadinside<foreach>, that transformed payload is lost after the scope closes. If you must retain results from each iteration, append them to a flow variable (e.g.,vars.resultsCollection).
[!WARNING] Parallel For Each Variable Trap In
<parallel-foreach>, variables set inside the scope do NOT propagate back to the parent flow. Only<foreach>propagates variable mutations back to the parent flow.
[!TIP] Counter Indexing The built-in counter (
vars.counter) is 1-indexed (first item is1, second is2), matching standard DataWeave and Mule conventions rather than 0-indexed Java arrays.
A Mule flow receives a JSON array of 5 customer objects. A Set Variable component initializes vars.processedCount to 0. A For Each scope processes the array sequentially, executing a Set Variable component that increments vars.processedCount by 1 and a Transform Message component that replaces payload with a new XML structure. What are the values of payload and vars.processedCount immediately after the For Each scope finishes execution?
A developer configures a For Each scope with batchSize="25" to process a collection of 100 purchase order records retrieved from a database. How many total iterations will the For Each scope execute, and what is the data type and size of payload inside each iteration?
A For Each scope is configured to process an array of 10 records without an internal Try scope or error handler. While processing the 4th record, an external HTTP Request connector throws an HTTP:CONNECTIVITY exception. What is the execution outcome of this flow?
An integration solution must extract 500,000 inventory records from an enterprise ERP database, execute two validation and mapping transformations, and upsert the records to Salesforce using the Bulk API in chunks of 200. The process must be fully resilient to system restarts, execute asynchronously without blocking the client HTTP trigger, and isolate record-level failures so invalid records do not abort valid ones. Which MuleSoft processing construct is best suited for this requirement?