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.
Last updated: August 2026

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:

  1. Single-Threaded & Synchronous: The For Each scope executes on the calling thread. It does not spawn background worker threads. Iteration N+1 never begins until iteration N has fully completed all message processors inside the scope.
  2. Payload Splitting: During each iteration, the payload is temporarily replaced with the single element being processed (or an array of elements if batchSize > 1).
  3. 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.
  4. 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:

AttributeTypeDefault ValueDescription
collectionDataWeave Expression#[payload]Expression defining the collection to iterate over. Can target nested collections (e.g., #[payload.orders]).
batchSizeInteger1Number of records grouped together in each iteration. When set to N > 1, payload inside the scope is a sub-array of N items.
rootMessageVariableNameStringrootMessageVariable name storing the original root Mule message during iteration. Accessible inside the scope via vars.rootMessage.
counterVariableNameStringcounterName 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 N elements.
  • Inside the <foreach> scope, payload is an Array containing up to N items.
  • vars.counter increments by 1 for each chunk/sub-array, not for each individual item.
  • The final chunk may contain fewer than N items if the collection length is not evenly divisible by batchSize.
+-----------------------------------------------------------------------------------------+
|                           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:

  1. The <foreach> scope immediately halts execution.
  2. All remaining, unprocessed items in the collection are discarded and never executed.
  3. The exception is routed directly to the enclosing flow's error handler (or global error handler).
  4. 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 a STREAM_MAXIMUM_SIZE_EXCEEDED or 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:

FeatureFor Each (<foreach>)Parallel For Each (<parallel-foreach>)Batch Job (<batch:job>)
Execution ThreadingSingle calling thread (Sequential)Multiple worker threads (Concurrent / Parallel)Dedicated thread pool across multiple steps (Asynchronous)
Payload After ScopeRestored to original inbound payloadAggregated array of iteration output messagesBatchJobResult summary object (in On Complete)
Variable ScopeModifications persist across iterations and outside scopeModifications are isolated to each thread and lost upon exitModifications become Record Variables; lost outside batch job
Error HandlingAborts immediately on first unhandled errorProcesses all routes, then throws MULE:COMPOSITE_ROUTINGConfigurable tolerance (maxFailedRecords, acceptPolicy)
Handoff ModeSynchronous (blocks calling flow)Synchronous (blocks until all parallel routes join)Asynchronous (immediate handoff to batch instance)
Queue PersistenceNone (in-memory only)None (in-memory only)Persistent Disk Queues (resilient across restarts)
Ideal Use CaseSmall/medium collections requiring ordered execution or variable accumulationMedium collections where independent I/O calls can run in parallelMassive 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 payload inside <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 is 1, second is 2), matching standard DataWeave and Mule conventions rather than 0-indexed Java arrays.

Test Your Knowledge

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
B
C
D
Test Your Knowledge

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
B
C
D
Test Your Knowledge

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?

A
B
C
D
Test Your Knowledge

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?

A
B
C
D