9.4 Validating Mule Events with the Validation Module
Key Takeaways
- The Validation module raises a typed VALIDATION:* error when a value fails a check, converting silent bad data into an error the flow can handle.
- Each validator has its own error type — is-true and is-false raise VALIDATION:INVALID_BOOLEAN, matches-regex raises VALIDATION:MISMATCH, is-not-null raises VALIDATION:NULL.
- The <validation:all> scope runs every nested validator and reports one summarizing VALIDATION:MULTIPLE error instead of stopping at the first failure.
- Every validator accepts an optional message parameter for a business-readable failure message, plus error mappings to re-map its error type.
- Validators are for rejecting invalid data; a Choice router is for sending valid data down different paths — the exam tests that distinction directly.
Validating Mule Events with the Validation Module
The Routing Events domain of the Salesforce Certified MuleSoft Developer I blueprint lists three objectives, and only two of them are routers. The third — apply correct processors/syntax to validate Mule events, and predict outcomes — is about the Validation module, a set of small components that assert something about the event and raise a typed error when the assertion fails. It sits in the routing domain because validation and routing are the two ways a flow reacts to message content: routing sends good data down different paths, validation stops bad data from travelling at all.
1. What a Validator Actually Does
A validator has exactly two outcomes. If the condition holds, the event passes through completely unchanged — payload, attributes, and variables are untouched, and the next processor runs. If the condition fails, the validator raises a typed error and normal Mule error handling takes over.
+-----------------------------------------------------------------------------------------+
| VALIDATOR EXECUTION OUTCOMES |
| |
| [ Mule Event ] ---> <validation:is-number value="#[payload.qty]"/> |
| | |
| +-------------------+-------------------+ |
| | | |
| (condition true) (condition false) |
| | | |
| v v |
| [ Event continues UNCHANGED ] [ VALIDATION:INVALID_NUMBER raised ] |
| payload/attributes/vars intact --> matching on-error-* scope handles it |
+-----------------------------------------------------------------------------------------+
That "unchanged" behavior matters for prediction questions. A validator never sets the payload to true, never wraps the payload in a result object, and never returns a Boolean into the flow. If a question shows a validator followed by a Logger printing #[payload], the Logger prints whatever the payload was before the validator.
2. The Validator Catalogue and Their Error Types
Every validator declares its own error type, which is what lets an error handler respond differently to each kind of bad input.
| Validator | XML element | Error type raised on failure |
|---|---|---|
| Is True | <validation:is-true> | VALIDATION:INVALID_BOOLEAN |
| Is False | <validation:is-false> | VALIDATION:INVALID_BOOLEAN |
| Is Number | <validation:is-number> | VALIDATION:INVALID_NUMBER |
| Is Email | <validation:is-email> | VALIDATION:INVALID_EMAIL |
| Is URL | <validation:is-url> | VALIDATION:INVALID_URL |
| Is Time | <validation:is-time> | VALIDATION:INVALID_TIME |
| Is IP | <validation:is-ip> | VALIDATION:INVALID_IP |
| Is Not Null | <validation:is-not-null> | VALIDATION:NULL |
| Is Null | <validation:is-null> | VALIDATION:NOT_NULL |
| Is Not Blank String | <validation:is-not-blank-string> | VALIDATION:BLANK_STRING |
| Is Blank String | <validation:is-blank-string> | VALIDATION:NOT_BLANK_STRING |
| Is Not Empty Collection | <validation:is-not-empty-collection> | VALIDATION:EMPTY_COLLECTION |
| Is Empty Collection | <validation:is-empty-collection> | VALIDATION:NOT_EMPTY_COLLECTION |
| Matches Regex | <validation:matches-regex> | VALIDATION:MISMATCH |
| Validate Size | <validation:validate-size> | VALIDATION:INVALID_SIZE |
| Is Elapsed / Is Not Elapsed | <validation:is-elapsed> / <validation:is-not-elapsed> | VALIDATION:NOT_ELAPSED_TIME / VALIDATION:ELAPSED_TIME |
Notice the two naming inversions that trip candidates up: is-not-null fails with VALIDATION:NULL (the error names the problem, not the check), and matches-regex fails with VALIDATION:MISMATCH rather than an "INVALID_REGEX" type.
<flow name="create-customer-flow">
<http:listener config-ref="HTTP_Listener_config" path="/customers" doc:name="Listener"/>
<validation:is-not-blank-string value="#[payload.lastName]"
message="lastName is required"
doc:name="Require Last Name"/>
<validation:is-email email="#[payload.email]"
message="#['Not a valid email address: ' ++ (payload.email default 'null')]"
doc:name="Validate Email"/>
<validation:matches-regex value="#[payload.postalCode]"
regex="^\d{5}(-\d{4})?$"
message="Postal code must be 5 or 9 digits"
doc:name="Validate Postal Code"/>
<db:insert config-ref="Database_Config" doc:name="Insert Customer">
<db:sql>INSERT INTO customers (last_name, email) VALUES (:ln, :em)</db:sql>
<db:input-parameters><![CDATA[#[{'ln': payload.lastName, 'em': payload.email}]]]></db:input-parameters>
</db:insert>
<error-handler>
<on-error-propagate type="VALIDATION:INVALID_EMAIL, VALIDATION:MISMATCH, VALIDATION:BLANK_STRING" doc:name="Bad Request">
<set-variable variableName="httpStatus" value="400" doc:name="Set 400"/>
<set-payload value="#[{ error: 'BAD_REQUEST', detail: error.description }]" doc:name="Error Body"/>
</on-error-propagate>
</error-handler>
</flow>
That error handler is the point of typed validation errors: three different validator failures all map to a single HTTP 400, while a DB:CONNECTIVITY error further down would not match and would fall through to a different handler and a 500.
3. Fail Fast vs. Collect Everything: all and any
By default, validators are sequential and fail fast — the first failure raises and the rest never execute. A client submitting a form with three bad fields would have to fix them one round trip at a time. The <validation:all> scope solves that: it runs every nested validator and reports one aggregated error.
<validation:all doc:name="Validate All Fields">
<validation:is-not-blank-string value="#[payload.firstName]" message="firstName is required"/>
<validation:is-not-blank-string value="#[payload.lastName]" message="lastName is required"/>
<validation:is-email email="#[payload.email]" message="email is invalid"/>
<validation:is-number value="#[payload.age]" message="age must be numeric"/>
</validation:all>
| Scope | Behavior | Error type raised |
|---|---|---|
| Sequential validators | Stops at the first failure | The specific type of the validator that failed |
<validation:all> | Runs all nested validators, then reports the combined result | VALIDATION:MULTIPLE |
<validation:any> | Succeeds if at least one nested validator passes | VALIDATION:MULTIPLE only when all of them fail |
[!IMPORTANT] all Always Raises VALIDATION:MULTIPLE Inside an
<validation:all>scope the individual types are collapsed: no matter which nested validators failed or how many, the scope reportsVALIDATION:MULTIPLE. An error handler written forVALIDATION:INVALID_EMAILwill therefore not catch a failure that occurred inside anallscope. The individual messages are still available through the error description.
4. Customizing the Failure
Two optional parameters are available on every validator:
message— a business-readable string (or DataWeave expression) that becomes the error description, replacing the generic technical text. This is what an API returns to its consumer.- Error mappings — re-map the raised type to a domain type, so a validation failure can surface as, for example,
ORDER:INVALID_QUANTITYand be handled alongside other order errors.
<validation:is-number value="#[payload.quantity]" message="quantity must be numeric" doc:name="Check Quantity">
<error-mapping sourceType="VALIDATION:INVALID_NUMBER" targetType="ORDER:INVALID_QUANTITY"/>
</validation:is-number>
Validators as DataWeave Functions
The module also exposes its checks as DataWeave functions using the ExtensionName::functionName form, which is useful inside a router condition where you want a Boolean rather than an error:
#[Validation::isEmail(payload.email)]
#[Validation::isNumber(payload.quantity)]
5. Choosing Between Validator, Choice, and raise-error
| Requirement | Correct component |
|---|---|
| Reject the message when a field is missing or malformed | Validation module validator |
| Send valid messages down different processing paths | <choice> router |
| Stop on a business rule with no matching validator (e.g. "item is out of stock") | <raise-error type="INVENTORY:OUT_OF_STOCK"/> |
| Report every field problem in one response | <validation:all> scope |
[!TIP] The Empty
<otherwise>Anti-Pattern A Choice router whose<otherwise>branch is empty does not stop or reject anything — the event simply passes through unchanged, and the invalid message continues downstream. When a scenario says execution must halt for invalid input, an empty<otherwise>is always the wrong answer; a validator or<raise-error>is the right one.
[!WARNING] Validators Raise Errors — Plan the Handler Because a failed validator raises rather than returns, a flow with no matching
on-error-*scope propagates a validation failure all the way to the HTTP Listener, which returns a 500 under default settings. An API that should answer 400 Bad Request must map theVALIDATION:*types explicitly, as in the example above.
A flow contains <validation:is-number value="#[payload.quantity]"/> followed by a Logger printing #[payload]. The incoming payload is {"sku": "ABC", "quantity": 12}. What does the Logger output?
An API validates four request fields with four sequential validators. The client submits a request in which the email is malformed AND the age is not numeric. Under the default sequential arrangement, what does the client learn?
A developer wraps four validators in a validation:all scope. The flow error handler declares <on-error-propagate type="VALIDATION:INVALID_EMAIL"> to return HTTP 400. A request arrives with a malformed email, and the client receives HTTP 500 instead. Why?
A business rule requires a flow to stop processing when an ordered item is out of stock — a condition with no corresponding validator in the Validation module. The team currently uses a Choice router whose <otherwise> branch is empty. What is wrong, and what should be used instead?