9.1 Choice Router: Conditional Expressions & Default Routing
Key Takeaways
- The Choice router (<choice>) evaluates conditional <when> branches in strict sequential, top-to-bottom order, executing only the first branch whose DataWeave expression evaluates to true (first-match semantics).
- Once a matching <when> branch is identified and executed, all subsequent <when> expressions are completely bypassed, preventing redundant evaluations or side effects.
- The <otherwise> branch serves as the default fallback route, executing if and only if every preceding <when> condition evaluates to false.
- If no <when> condition matches and the <otherwise> branch contains no processors, the incoming Mule event passes through the Choice router completely unmodified without throwing an error.
- All modifications made to payload, attributes, and flow variables (vars) inside the executed branch persist downstream into subsequent message processors after exiting the Choice router.
Choice Router: Conditional Expressions & Default Routing
In event-driven integration architectures, routing messages dynamically based on business content, headers, or environmental state is a fundamental capability. In Mule 4, the Choice Router (<choice>) provides content-based routing functionality, operating analogously to an if-else if-else control structure in programming languages. Understanding its evaluation mechanics, first-match semantics, expression syntax, and event state lifecycle is critical for the Salesforce Certified MuleSoft Developer I exam.
1. Choice Router Architecture & First-Match Semantics
The Choice router routes a single incoming Mule event through one of several execution paths based on conditional DataWeave boolean expressions defined within <when> child elements. A mandatory or optional <otherwise> block provides a default execution path when none of the conditions evaluate to true.
+-----------------------------------------------------------------------------------------+
| CHOICE ROUTER ARCHITECTURE |
| |
| [ Incoming Mule Event ] |
| (payload, attributes, vars) |
| | |
| v |
| +-----------------------------------+ |
| | <choice> Container | |
| +-----------------------------------+ |
| | |
| [1. Evaluate when expression #1] |
| / \ |
| (true) (false) |
| / \ |
| +---------------------------+ [2. Evaluate when expression #2] |
| | Execute Branch 1 Pipeline | / \ |
| +---------------------------+ (true) (false) |
| | / \ |
| | +---------------------------+ [3. Evaluate ... ] |
| | | Execute Branch 2 Pipeline | | |
| | +---------------------------+ (false) |
| | | | |
| | | +--------------------------+ |
| | | | Execute <otherwise> Path | |
| | | +--------------------------+ |
| | | | |
| +------------------------+----------------------------+ |
| | |
| v |
| [ Outgoing Modified Mule Event ] |
| (payload, attributes, vars updated) |
+-----------------------------------------------------------------------------------------+
Core Operational Rules:
- Sequential Evaluation (Top-to-Bottom): Mule evaluates
<when>expressions in the exact order they are declared in the XML configuration. - First-Match Short-Circuiting: The moment a
<when>expression evaluates totrue, Mule routes the event into that branch's scope. No further<when>expressions are evaluated. Even if a subsequent branch condition would also evaluate totrue, it is completely ignored. - Single Path Execution: Exactly one branch is executed per Mule event (either one
<when>branch or the<otherwise>branch). - Unmatched Pass-Through: If none of the
<when>conditions match and no<otherwise>block contains message processors, the event passes through the router unchanged.
2. Choice Router XML Configuration
In Mule XML, a Choice router is declared using the <choice> element enclosing one or more <when> elements and an <otherwise> element:
<flow name="process-order-routing-flow" doc:name="Process Order Routing Flow">
<http:listener config-ref="HTTP_Listener_config" path="/orders" doc:name="Listener"/>
<!-- Initialize flow variable -->
<set-variable variableName="routingStatus" value="INITIALIZED" doc:name="Set Status"/>
<choice doc:name="Route by Order Tier and Value">
<!-- Branch 1: High Priority VIP Orders -->
<when expression="#[payload.customerTier == 'VIP' and payload.orderTotal >= 1000]">
<logger level="INFO" message="Routing to VIP Expedited Queue" doc:name="Log VIP"/>
<set-variable variableName="routingStatus" value="EXPEDITED_VIP" doc:name="Set VIP Status"/>
<set-payload value="#[{ 'orderId': payload.orderId, 'status': 'PROCESSED_PRIORITY', 'discount': 0.15 }]" doc:name="VIP Payload"/>
</when>
<!-- Branch 2: Standard High-Value Orders -->
<when expression="#[payload.orderTotal >= 500]">
<logger level="INFO" message="Routing to Standard High-Value Processing" doc:name="Log High Value"/>
<set-variable variableName="routingStatus" value="STANDARD_HIGH" doc:name="Set High Status"/>
<set-payload value="#[{ 'orderId': payload.orderId, 'status': 'PROCESSED_STANDARD', 'discount': 0.05 }]" doc:name="Standard Payload"/>
</when>
<!-- Branch 3: International Orders -->
<when expression="#[payload.shippingAddress.country != 'US' and !isEmpty(payload.shippingAddress.country)]">
<logger level="INFO" message="Routing to International Customs Pipeline" doc:name="Log International"/>
<set-variable variableName="routingStatus" value="INTERNATIONAL" doc:name="Set Intl Status"/>
</when>
<!-- Fallback Default Branch -->
<otherwise>
<logger level="INFO" message="Routing to Default Bulk Fulfillment" doc:name="Log Default"/>
<set-variable variableName="routingStatus" value="DEFAULT_BULK" doc:name="Set Default Status"/>
</otherwise>
</choice>
<!-- Subsequent components receive modifications made in the chosen branch -->
<logger level="INFO" message="#['Routing completed with status: ' ++ vars.routingStatus]" doc:name="Log Final Result"/>
</flow>
[!IMPORTANT] XML Entity Escaping for Comparison Operators When writing comparison operators in Mule XML files, remember that XML requires character escaping for angle brackets:
- Use
>for greater than (>)- Use
>=for greater than or equal to (>=)- Use
<for less than (<)- Use
<=for less than or equal to (<=) In Anypoint Studio's visual canvas, entering>or<into the expression builder will automatically escape them in the underlying XML configuration.
3. DataWeave Boolean Expression Rules & Null Safety
The expression inside expression="#[...]" must evaluate to a valid Boolean (true or false). If the expression returns a non-boolean type (such as a String, Number, or Object) or throws an unhandled exception during evaluation, Mule raises a runtime expression error (MULE:EXPRESSION).
Common DataWeave Expressions for Routing
| Expression Scenario | DataWeave Syntax inside expression="#[...]" | Explanation |
|---|---|---|
| String Equality | payload.status == 'ACTIVE' | Case-sensitive string comparison. |
| Case-Insensitive Equality | lower(payload.status) == 'active' | Normalizes string casing before matching. |
| Numeric Comparison | payload.amount > 100 | Checks if numeric field exceeds threshold. |
| Null-Safe Property Access | payload.customer.?tier == 'GOLD' | Uses null-safe navigation (.?) to prevent null reference errors. |
| Default Fallback on Missing Field | (payload.country default 'US') == 'US' | Provides fallback string if field is null or undefined. |
| Collection Emptiness Check | !isEmpty(payload.lineItems) | Verifies array contains at least one item. |
| Array Size Check | sizeOf(payload.orders) >= 5 | Compares number of elements in array. |
| Collection Membership | ['US', 'CA', 'MX'] contains payload.country | Checks if value exists within a static array. |
| Regular Expression Match | payload.postalCode matches /^\d{5}(-\d{4})?$/ | Evaluates regex pattern matching against string. |
| Flow Variable Inspection | vars.retryCount < 3 and vars.isAuthorized == true | Combines multiple variable checks with boolean and. |
| Inbound Query Parameter Check | attributes.queryParams.priority == 'urgent' | Evaluates HTTP Listener inbound query parameters. |
Handling Nulls & Undefined Fields
In production integrations, incoming JSON or XML payloads frequently omit optional fields. If an expression attempts to navigate nested properties on a missing parent object without safe navigation, it will cause runtime errors.
// UNSAFE: Throws exception if customer or address is null
#[payload.customer.address.country == 'US']
// SAFE: Using null-safe navigation (?)
#[payload.customer.?address.?country == 'US']
// SAFE: Using default operator
#[(payload.customer.address.country default '') == 'US']
4. Scope & Event State Persistence
A critical concept on the Developer I certification is how the Mule event behaves when entering and exiting a Choice router branch.
+-----------------------------------------------------------------------------------------+
| EVENT PERSISTENCE THROUGH CHOICE ROUTER |
| |
| [BEFORE CHOICE] |
| payload = { "orderId": 1001, "total": 600 } |
| attributes = { queryParams: { channel: "WEB" } } |
| vars = { trackingId: "TRK-88", status: "NEW" } |
| | |
| v |
| [INSIDE MATCHED WHEN BRANCH] |
| <set-payload value="#[{ 'orderId': 1001, 'status': 'APPROVED' }]" /> |
| <set-variable variableName="status" value="'PROCESSED'" /> |
| <set-variable variableName="approvalCode" value="'AUTH-999'" /> |
| | |
| v |
| [AFTER CHOICE] |
| payload = { "orderId": 1001, "status": 'APPROVED' } <-- (OVERWRITTEN) |
| attributes = { queryParams: { channel: "WEB" } } <-- (PRESERVED) |
| vars = { trackingId: "TRK-88", <-- (PRESERVED) |
| status: "PROCESSED", <-- (UPDATED) |
| approvalCode: "AUTH-999" } <-- (NEW VARIABLE ADDED) |
+-----------------------------------------------------------------------------------------+
State Rules:
- Direct In-Line Modification: Message processors inside the executed branch modify the actual Mule event in place.
- Downstream Propagation: When the branch completes and execution continues to the component immediately following the
</choice>tag, all changes topayload,attributes, andvarspersist. - Unexecuted Branches: Components in unexecuted
<when>branches or<otherwise>are completely ignored and exert zero effect on the Mule event.
5. Exam Watch: Core Choice Router Scenarios
[!IMPORTANT] Order of Branches Matters Always order
<when>branches from most specific to most general. If a broad condition (e.g.,payload.amount > 0) is placed before a specific condition (e.g.,payload.amount > 1000 and payload.vip == true), the broad branch will capture the event first, and the specific branch will never execute.
[!WARNING] Choice Router Does Not Throw Routing Errors on Unmatched Events Unlike routers in some other integration frameworks, if a Mule 4 Choice router encounters an event where no
<when>condition evaluates totrueand<otherwise>has no processors, it does not throw a routing error. The event passes through unmodified.
[!TIP] Choice vs Filter A Choice router directs message flow into different execution paths. If your goal is simply to halt flow execution when a condition is not met, use a Validation Module component (such as
<validation:is-true>) or raise a custom error (<raise-error>) rather than an empty<otherwise>block.
A Mule flow processes an incoming order payload: { "orderType": "STANDARD", "orderTotal": 1500, "customerTier": "VIP" }. The flow contains a Choice router configured with the following branches in sequential order:
What value is assigned to vars.shipping after the Choice router finishes executing?
A developer needs to configure a Choice router condition to check whether an incoming customer record has an account tier of 'GOLD' or 'PLATINUM'. In some incoming messages, the customer object or its tier field may be null or completely missing from the JSON payload. Which DataWeave expression safely evaluates the condition without throwing an expression error at runtime?
A Mule flow receives an HTTP POST request with payload { "country": "JP", "items": 3 }. Before the Choice router, the flow sets vars.region = "GLOBAL". The Choice router contains a matching when branch for payload.country == 'JP'. Inside this branch, a Set Variable component updates vars.region to "APAC" and a Set Payload component changes payload to { "status": "CONFIRMED", "warehouse": "TOKYO" }. What are the values of payload and vars.region immediately after exiting the Choice router?
A developer configures a Choice router with two <when> branches checking for order currencies: payload.currency == 'USD' and payload.currency == 'EUR'. The <otherwise> element is present but contains no message processors. An incoming event arrives with payload.currency == 'CAD'. What happens when this event executes through the Choice router?