TL;DR
- Your
fundeclaration works inline but breaks with "Unable to resolve reference" when moved to a.dwlmodule - Module files cannot contain
outputor---— delete those lines and it works - The error message lies — it says "resolve reference" not "invalid module structure"
- Recursive custom functions like hand-rolled
powwill stack overflow in production - Higher-order functions taking lambdas need explicit type annotations or imports fail silently
I had 4 Mule flows computing the same pricing logic last year. Discounts, tax rounding, shipping thresholds. The functions worked perfectly inline in each flow. Copy-paste across 4 flows. Classic duplication.
So on a Friday afternoon, I did the obvious refactor: extract the functions into a shared .dwl module file. Import once, use everywhere.
It broke immediately. 2 hours of debugging a misleading error message.
The Working Inline Code
Here's what I had in each flow, working perfectly:
%dw 2.0
output application/json
fun roundTo(num: Number, places: Number): Number = (num * pow(10, places) as Number) / pow(10, places)
fun pow(base: Number, exp: Number): Number = if (exp <= 0) 1 else base * pow(base, exp - 1)
fun applyDiscount(price: Number, discountFn: (Number) -> Number): Number = discountFn(price)
var discount = (p: Number) -> if (p > 500) p * 0.9 else p
---
payload.products map (p) -> ({
name: p.name,
originalPrice: p.price,
discounted: roundTo(applyDiscount(p.price, discount), 2),
tax: roundTo(applyDiscount(p.price, discount) * payload.taxRate, 2),
freeShipping: p.price >= payload.freeShippingThreshold
})
Three custom functions: roundTo for decimal precision, pow as a recursive helper, and applyDiscount — a higher-order function that takes a lambda for the discount strategy. Clean input/output:
{
"products": [
{"name": "Laptop Pro", "price": 1299.99},
{"name": "Standing Desk", "price": 799},
{"name": "Wireless Mouse", "price": 29.99},
{"name": "Office Chair", "price": 449}
],
"taxRate": 0.0875,
"freeShippingThreshold": 100
}
Output: each product with computed discount, tax, and shipping flag. No issues.
100 production-ready DataWeave patterns with tests: mulesoft-cookbook on GitHub
Trap 1: The Module Structure Constraint
I copied the entire script into src/main/resources/modules/pricing.dwl. Imported it:
import * from modules::pricing
Error: "Unable to resolve reference of pricing"
The function was right there. The path was correct. I checked classpath, file encoding, permissions. Nothing worked.
The problem: my module file still had output application/json and the --- separator from the inline script. Module files cannot contain these. DataWeave modules only allow:
-
fundeclarations -
vardeclarations -
typedefinitions -
namespacedefinitions
No output. No ---. No body expression.
The correct module file:
fun roundTo(num: Number, places: Number): Number = (num * pow(10, places) as Number) / pow(10, places)
fun pow(base: Number, exp: Number): Number = if (exp <= 0) 1 else base * pow(base, exp - 1)
fun applyDiscount(price: Number, discountFn: (Number) -> Number): Number = discountFn(price)
var discount = (p: Number) -> if (p > 500) p * 0.9 else p
Delete %dw 2.0, delete output application/json, delete ---, delete the body. Keep only declarations.
Trap 2: Recursive Functions Without Depth Limits
The pow function is recursive:
fun pow(base: Number, exp: Number): Number = if (exp <= 0) 1 else base * pow(base, exp - 1)
In testing with exponent 2 or 3, it works. In production, a pricing rule with exponent 500 hits the call stack limit. DataWeave doesn't have tail-call optimization for this pattern.
The fix: use dw::util::Math functions instead of hand-rolling recursion for anything that touches production data. Built-in functions handle edge cases you haven't thought of.
Trap 3: Higher-Order Function Type Errors
applyDiscount takes a lambda parameter:
fun applyDiscount(price: Number, discountFn: (Number) -> Number): Number = discountFn(price)
If someone imports this and passes the wrong function signature — say a function that takes two arguments — the error is cryptic. DataWeave checks types at the call site, but the error message references the module internals, not the caller's mistake.
Always type-annotate function parameters explicitly. (Number) -> Number tells the caller exactly what shape of lambda to pass.
What Module Files Can and Cannot Contain
| Allowed in modules | NOT allowed in modules |
|---|---|
fun declarations |
output directive |
var declarations |
--- separator |
type definitions |
Body expressions |
namespace definitions |
%dw 2.0 header |
This constraint exists because modules are imported, not executed. They provide definitions for other scripts to use. They don't produce output themselves.
What I Do Now
Before deploying any shared .dwl module, I run a 30-second check:
- Does the file contain
output? Remove it. - Does the file contain
---? Remove it. - Does any function use recursion? Replace with built-in.
- Are all function parameters type-annotated? If not, add types.
Would have saved me those 2 Friday hours.
100 patterns with MUnit tests: github.com/shakarbisetty/mulesoft-cookbook
60-second video walkthroughs: youtube.com/@SanThaParv
Top comments (0)