DEX-401 · 60 QUESTIONS · 120 MIN · 70% TO PASS

SCMD Exam Domains

12 weighted sections across 4 conceptual layers. ⚑ High-stakes sections account for ~65% of the exam. Hover any domain to see key concepts and illuminate related sections.

Design & Lifecycle
App Structure
Data Processing
Flow Control
 High-stakes — tested most heavily
- - - Conceptual synergy
SCMD · SECTION 03 · 10% WEIGHT · ROOT CAUSE OF MANY MISSES

The Mule Event Model

One event flows through the flow, mutating as it goes. The key to the exam is understanding what is inside what, who can change it, and what survives a scope boundary.

Mule Event · one event, evolving through the flow
Mule Message · the data in transit
payload MUTABLE
The message body. The primary data flowing through the app.
Set Payload / Transform Message
HTTP Request → response body
DB Select → Java List of Maps
JMS Publish → UNCHANGED
For Each → RESTORED after scope
target= set → payload PRESERVED
attributes READ-ONLY
Source metadata. Set by the source; replaced after HTTP Request.
attributes.uriParams.ID
attributes.queryParams.dest
attributes.headers.'content-type'
attributes.method
→ REPLACED after HTTP Request
attributes.'http.uri.params'.x
PEER — NOT INSIDE MULE MESSAGE
vars MUTABLE
Your scratchpad. Flow-scoped. Set by Set Variable or target attribute. Peer of Mule Message — not inside it.
vars.myVar
variables.x
flowVars.x
Survives HTTP Request
Survives Flow Ref
Cross-execution → Object Store
error · populated only when an error occurs — null on the happy path
error ERROR SCOPE ONLY
Null during normal execution. Accessible only inside On Error Continue / On Error Propagate handlers.
error.errorType.namespace → "HTTP"
error.errorType.identifier → "NOT_FOUND"
error.description → message string
error.cause → Java exception
Catch all HTTP: Type=(empty) When: #[error.errorType.namespace == "HTTP"]
Sources — Create New Events
HTTP Listener CREATES

payload = request body
attributes = headers, URI/query params, method
vars = empty initially

Scheduler CREATES

payload = null · attributes = minimal
No incoming message — time-driven only

JMS / File Listener CREATES

payload = message / file content
attributes = source metadata

payload
attributes.uriParams.id
attributes.queryParams.dest
attributes.headers.'content-type'
vars.myVar
error.errorType.namespace
Mule 3 — Eliminate on Sight

inboundProperties · outboundProperties
flowVars · sessionVars
attributes.'http.uri.params'.x
message.payload

Connectors — Mutate the Event
HTTP Request REPLACES

payload → response body
attributes → response attrs
vars → PRESERVED ✓ always

DB Select REPLACES

payload → Java List of Maps
No rows → [] not null, not false
Needs Transform Message for JSON output

JMS Publish PRESERVES

Fire-and-forget. payload unchanged.
Publish-Consume → payload = reply

Target Attribute

target="varName" → result in vars.varName, original payload PRESERVED

Scopes & Routers
For Each

Input must be Array — Map/Object = zero iterations. Each iteration: element is payload. After scope: original payload restored. Vars set inside persist outside.

Scatter-Gather

Sends entire event to ALL routes in parallel. Output: Object (not Array) with keys "0","1","2". Access: payload[0].payload. After: attributes = null.

Flow Reference

Same Mule event. payload + attributes + vars all travel both directions. No network boundary — internal only.

Sub-flow vs Private flow

Sub-flow: always uses calling flow's error handler — no own handler.
Private flow: can have its own handler.

Scope Boundaries — What Survives?
Boundary Type payload attributes vars Key Rule
Flow Reference (internal) Same Mule event object. Full bidirectional visibility.
HTTP Request (network) REPLACED
→ response body
REPLACED
→ response attrs
vars are the ONLY element that crosses a network boundary intact.
For Each scope → element
restored after
✓ set inside persist outside Input must be Array. Sequential, not parallel.
Batch Step (per record) → record ✗ don't reach On Complete On Complete = BatchJobResult summary, NOT records. Parallel.
Sub-flow (via Flow Ref) Same as Flow Reference. Sub-flow ALWAYS uses calling flow's error handler.
SCMD · SECTION 09 · 10% WEIGHT · HIGH STAKES ⚑

Routing Events

Four routers with fundamentally different behaviors. Scatter-Gather's Object output structure is the single most-missed concept. Choice Router expression syntax trips on single-equals and Mule 3 patterns.

All Four Routers — Compared
RouterRoutes executedExecution modelOutput / result
Choice Router ONE — first matching condition Top → bottom, first match wins Payload from matched route. Default runs when NO condition matches.
Scatter-Gather ALL — always, no exceptions Parallel Object keys "0","1","2"… Each value = full Mule message. attributes = null after.
First Successful ONE — first that doesn't throw Sequential, stops on first success Result of the first route that completes without error. Remaining routes skipped.
Round Robin ONE — rotates per message Each new message → next route in sequence Load distribution across identical processors. Stateful rotation.
Scatter-Gather — Deep Dive
Critical Rules
Output is an OBJECT — never an Array

String keys "0", "1", "2"… one per route. Each value is a full Mule message with .payload + .attributes.
Access: payload[0].payload, payload[1].payload

Entire event sent to ALL routes — in parallel

Not split. Every route receives the same complete Mule event simultaneously. SG waits for ALL routes to finish before continuing.

After completion

attributes = null · payload = assembled Object · vars from before are still accessible

// Output structure after Scatter-Gather
payload = {
  "0": { payload: route0Result, attributes: ... },
  "1": { payload: route1Result, attributes: ... },
  "2": { payload: route2Result, attributes: ... }
}
attributes = null
Partial Failure — Try Scope Pattern
Graceful degradation per route

Wrap each route in a Try scope with On Error Continue → Transform []. A failed route returns an empty array instead of failing the whole Scatter-Gather.

Scatter-Gather
  Route 0: [Try] → getAmericanFlights
            [On Error Continue → Transform []]
  Route 1: [Try] → getUnitedFlights  (same)
  Route 2: [Try] → getDeltaFlights   (same)

→ Partial results assembled even if 1–2 routes fail
Choice Router
Evaluation Rules
Top-to-bottom — first match wins

Like firewall rules. Only ONE branch executes. Default branch runs when NO condition matches — it is not an error fallback.

When-Expression Syntax
✓ CORRECT
#[ 'MuleSoft' == payload.company ]

#[ vars.airline == "american" ]
✗ WRONG
#[company = "MuleSoft"]
single = is assignment

#[if(payload == "x")]
no if() wrapper needed
Validate Component
A Gate, Not a Transform
How it works

Evaluates a DataWeave boolean.
TRUE → event passes through unchanged.
FALSE → throws error with configurable type (e.g. APP:INVALID_DESTINATION)
Does NOT return a value or modify the event on success.

Think of it as an assertion

On failure it throws — the flow's error handler catches it. On success the flow continues as if Validate wasn't there.

APIKit + Routing
APIKit validates BEFORE Choice Router fires

Invalid enum value → APIKIT:BAD_REQUEST thrown at the APIKit Router. The Choice Router's when-expression is never evaluated.

SCMD · SECTION 07 · 10% WEIGHT · HIGH STAKES ⚑

Processing Records

For Each and Batch share a surface similarity but differ completely in execution model, scoping, and error behavior. On Complete receiving a summary report (never records), and Max Failed Records = 0 defaulting to zero-tolerance, are the persistent exam traps.

For Each vs Batch — Side by Side
FeatureFor EachBatch Job
ExecutionSequential — array order, one at a timeParallel — records processed concurrently
Input type requiredArray only — Object/Map = zero iterations, no error thrownAny collection — auto-split into individual records
Payload inside scopeCurrent array elementCurrent record (per batch step)
Payload after scopeRestored to pre-scope payloadN/A — async handoff; outer payload unaffected
Variables set insideAccessible outside (last iteration value)Don't propagate to On Complete or outer flow
Error behaviorStops entire scope on first errorRecord marked failed; others continue (see Max Failed Records)
counter variableAvailable inside; removed after scope exitsN/A
Use caseSmall collections, synchronous, in-memoryLarge datasets, files, DB result sets — high volume async
Batch Job — Three Phases
Input
Collection arrives. Auto-split into individual records.

Outer flow: async handoff begins — client response must be sent before this point.
IMPLICIT PHASE
Batch Step(s)
Each step runs every record. Multiple steps are sequential per record. Records within a step run in parallel.

Failed record: marked failed, skipped in subsequent steps — others continue.
PARALLEL PER RECORD
On Complete
Runs once after all records finish.

Payload = BatchJobResult — a summary report (counts, job ID).
Never the processed records.
RUNS ONCE
Critical Gotchas
Max Failed Records = 0 means ZERO tolerance

Default 0 = zero failures allowed. If ANY record fails, the entire batch stops. This is the opposite of what "zero" implies intuitively.

On Complete ≠ client response

Batch is asynchronous. Send a 202 Accepted response before the async handoff. On Complete fires long after the client connection has closed.

For Each + Object input = silent no-op

Passing an Object/Map produces zero iterations with no error. The scope is silently skipped. Input must be an Array.

Scheduler fixedFrequency — no waiting

Fires on schedule regardless of whether the prior execution is still running. Does not wait.

Watermarking Pattern
Why Object Store — not a variable
Variables reset each execution

Object Store persists across executions — exactly what watermarking needs to track the last processed ID between Scheduler runs.

// Manual watermark flow
1. OS Retrievevars.lastID
2. DB Select WHERE id > #[vars.lastID]
3. For Each / Batch → process records
4. OS Store → save new max ID

✓ Object Store: persists across executions
✗ Variable: resets each run — useless for watermark
For Each Payload Rule
Payload restored after scope exits

Whatever For Each was iterating, the original payload is restored once the scope exits — regardless of what Set Payload calls happened inside. Variables set in the last iteration are accessible outside.

SCMD · SECTION 08 · 15% WEIGHT · HIGHEST WEIGHT ⚑

Transforming Data with DataWeave

Highest-weighted section. Every trap below has appeared on exams. fun keyword, :: module separator, @() XML attributes, and operation chaining order are the persistent failure points.

Script Anatomy
// Canonical DW 2.0 script
%dw 2.0
output application/json
import modules::Utility
type DateYMD = Date { format: "yyyy/MM/dd" }
fun greet(name: String) = "Hi " ++ name
---
{ result: greet(payload.name) }
Header Sections
%dw 2.0

Version declaration — always first line.

output application/json

Output MIME type — controls serialization. Default between processors is application/java.

---

Separator between header and body. Body is the single expression producing the output.

Syntax Traps — All High Exam Frequency
Function Definition
✓ CORRECT
fun newCode(id: Number) =
  "PC-" ++ (id as String)
✗ WRONG
function newCode(id) ->
wrong keyword + wrong arrow

var newCode = (id) ->
var + lambda ≠ fun
Keyword: fun · Assign: = · Types: Capitalized · No return
Module Import & Call
✓ CORRECT
import modules::Utility
---
Utility::pascalize("max mule")

// or import all:
import * from modules::Utility
pascalize("max mule")
✗ WRONG
import modules.Utility
dot ≠ double colon

Utility.pascalize()
dot notation for call

import Utility
missing modules:: path
Separator is always :: — both in path and in call syntax
XML Attribute Syntax
✓ CORRECT
employee @(
  firstName: payload.first,
  lastName: payload.last
): null
✗ WRONG
employee (firstName)
missing @

fname: x; lname: y
semicolon ≠ comma

employee @(...): ""
value must be null
Wrapper: @() · Separator: comma · Value: null
Type Coercion & Custom Types
✓ CORRECT
42 as String

20.38 as String { format: ".##" }
// curly braces, not parens

type DateYMD = Date { format: "yyyy/MM/dd" }
✗ WRONG
42 as string
lowercase types

20.38 as String (format: ".##")
parens ≠ curly braces

typedef DateYMD : Date...
wrong keyword + colon
Types always Capitalized · Format constraints use {} not ()
Operators — Return Types & Chaining Order
OperatorReturnsUse
mapARRAYTransform each element of an Array
filterARRAYKeep elements matching condition
flatMapARRAYmap then flatten one level
orderByARRAYSort Array or Object by key
pluckARRAYTransform Object entries → Array
groupByOBJECTGroup Array by key — keys = group values
reduceANYAccumulate Array to single value
++SAME TYPEConcat Strings/Arrays/Objects (types must match)
Chaining Order — Order Matters
1ST
filter
→ Array
2ND
orderBy
→ Array
3RD
groupBy
→ Object
payload filter $.price < 500
       orderBy $.price
       groupBy $.toAirport
Order matters

groupBy first = wrong because it returns an Object, which filter/orderBy cannot process.

++ type rule

Concatenates Strings, Arrays, or Objects — but types must match. String ++ Object = type error at runtime.

Quick-Fire Trap Table
If you see…The correct answer is…
fun keywordfun · assignment with = · no return keyword · types always Capitalized
Module import path separator:: double colon — both in import path and call syntax (modules::Utility, Utility::fn())
XML attribute wrapper@() · comma between attrs · element value is null, not empty string
String concatenation++ not + · String ++ Object throws type error
groupBy return typeObject — keys are group values, values are Arrays. Never an Array itself.
filter / map return typeArray — always, both of them
Type coercion syntaxas String · as Number · always Capitalized — never as string
Format constraint syntaxCurly braces: as String { format: ".##" } — never parentheses
Operation chaining orderfilter → orderBy → groupBy — wrong order gives wrong result or type error
typeOf() on Set Payload literalAlways returns String regardless of what the content looks like
Custom type definition keywordtype MyName = BaseType { constraint } · not typedef, not colon
SCMD · SECTION 10 · 10% WEIGHT · HIGH STAKES ⚑

Handling Errors

The most conceptually tricky section. On Error Continue does not resume execution after the failing processor. The global handler bypass rule catches everyone. HTTP:* wildcard doesn't work in the Type field.

Resolution Hierarchy — First Match Wins
CHECKED
1st
Try Scope Handler
If the error occurred inside a Try scope, that scope's error handler fires first. On Error Continue in a Try scope → flow continues after the Try scope — the only scenario where execution resumes post-error.
TRY SCOPE
CHECKED
2nd
Flow-Level Error Handler
If the flow has its own error handler and none of its scopes match the error type — the global handler is bypassed entirely. Error propagates unhandled to HTTP Listener → 500.
FLOW LEVEL
If a flow has ANY own error handler — even if nothing matches — the global handler is never consulted. No match = unhandled propagation, not global fallback.
CHECKED
3rd
Global Error Handler
Applied only if the flow has NO error handler defined at all. Must be configured as a global element — not in pom.xml, not in properties file.
GLOBAL
On Error Continue vs On Error Propagate
On Error Continue ABSORBS ERROR
Flow completes normally?Yes — treated as success
Processors after error point run?NO — skipped permanently
What calling flow receivesHandler's payload as result
HTTP Listener returns200 + handler payload
Calling flow sees error?No — transparent
The "Continue" trap

"Continue" means the handler runs and the flow exits normally. It does NOT mean execution resumes at the processor after the failing one. Those processors never run.

On Error Propagate RE-THROWS
Flow completes normally?No — error re-thrown after handler
Processors after error point run?NO — also skipped
What calling flow receivesOriginal error message
HTTP Listener returns500 + original error message
Calling flow sees error?Yes — bubbles up
Propagate payload trap

Even if you Set Payload inside the handler, the HTTP Listener returns the original error message — not the Set Payload value.

Error Matching Rules
Type FieldWhen FieldWhat it catches
HTTP:NOT_FOUNDSpecific error type only
ANYEverything — use as the last handler
(empty)#[error.errorType.namespace == "HTTP"]All HTTP:* — the only valid wildcard pattern
HTTP:*Does NOT work — wildcard invalid in Type field
First-match ordering (firewall rule)

Multiple handlers: top to bottom, first match fires, rest ignored. Put specific types first, ANY last.

No handler configured at all?

Mule default handler fires automatically — always present. Behaves like On Error Propagate: logs error, returns 500.

Flow Type — Error Isolation
Flow
Has source?Yes
Own error handler?Yes (optional)
Uses caller's?No
Private Flow
Has source?No
Own error handler?Yes (optional)
Uses caller's?If no own handler
Sub-flow
Has source?No
Own error handler?Never
Uses caller's?Always
Sub-flow vs Private flow

Sub-flows always use the calling flow's error handler — they cannot have their own. Private flows can have their own. Heavily tested distinction.

APIKit Auto-Generated Error Handlers
APIKIT:NOT_FOUND
404
Resource not found
APIKIT:BAD_REQUEST
400
Invalid param / enum value
APIKIT:METHOD_NOT_ALLOWED
405
HTTP method not in RAML
APIKIT:NOT_ACCEPTABLE
406
Accept header mismatch
APIKIT:UNSUPPORTED_MEDIA_TYPE
415
Content-Type not supported
APIKIT:NOT_IMPLEMENTED
501
No flow for this resource
Quick-Fire Recap
If you see…The answer is…
On Error Continue — does flow resume after error?No. Handler runs, its output = flow result, flow exits normally. Subsequent processors never execute.
On Error Propagate — what does HTTP Listener return?Original error message — even if Set Payload ran in the handler.
Flow has own handler but no scope matches the errorGlobal handler bypassed. Error propagates unhandled → HTTP Listener → 500.
No error handler configured at allMule default handler fires automatically — always present. Behaves like Propagate → 500.
HTTP:* wildcard in Type fieldDoes NOT work. Use: Type = (empty) + When = #[error.errorType.namespace == "HTTP"]
Child private flow — On Error Continue catches errorCalling flow never sees the error. Gets handler's payload as Flow Reference result. Continues normally.
Child private flow — On Error PropagateError bubbles up to calling flow's error handler.
Sub-flow error handlerSub-flows never have their own. Always uses calling flow's handler.
Try scope — On Error Continue insideFlow continues after the Try scope — the only scenario where flow continues after an error.
Global handler location configMust be a global element — not pom.xml, not properties file.