DEV Community

ThaSha
ThaSha

Posted on

DataWeave Custom Functions: The Module Trap That Wastes Hours

TL;DR

  • Your fun declaration works inline but breaks with "Unable to resolve reference" when moved to a .dwl module
  • Module files cannot contain output or --- — delete those lines and it works
  • The error message lies — it says "resolve reference" not "invalid module structure"
  • Recursive custom functions like hand-rolled pow will 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
})
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  • fun declarations
  • var declarations
  • type definitions
  • namespace definitions

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Does the file contain output? Remove it.
  2. Does the file contain ---? Remove it.
  3. Does any function use recursion? Replace with built-in.
  4. 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)