DEV Community

Balachandra Shakar
Balachandra Shakar

Posted on • Edited on

Migrating from DataWeave 1.0 to 2.0: A Practical Guide

If you're upgrading from Mule 3 to Mule 4, every DataWeave file needs to be rewritten. DW 2.0 is not backwards-compatible with 1.0 — the syntax changed, type names changed, flow control changed, and the module system is completely new.

I've migrated dozens of Mule projects and compiled the most common changes, gotchas, and anti-patterns into a comprehensive migration guide. Here's the practical version.


The Big Syntax Changes

1. Header Declarations Lost the %

DW 1.0 used % prefixes for everything in the header. DW 2.0 dropped them.

What DW 1.0 DW 2.0
Version %dw 1.0 %dw 2.0
Output %output application/json output application/json
Variables %var x = 42 var x = 42
Functions %function name(args) fun name(args) =
Namespaces %namespace ns http://... ns ns http://...

DW 1.0:

%dw 1.0
%output application/json
%var threshold = 100
%function isActive(emp) emp.status == "ACTIVE"
---
payload filter isActive($) filter $.salary > threshold
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

%dw 2.0
output application/json
var threshold = 100
fun isActive(emp) = emp.status == "ACTIVE"
---
payload filter isActive($) filter $.salary > threshold
Enter fullscreen mode Exit fullscreen mode

Note: %dw 2.0 keeps the % — it's the only header line that does. And fun uses = before the body.


2. Flow Control: when/otherwise to if/else

This is the change that breaks the most code.

DW 1.0:

value: payload.amount when payload.amount > 0 otherwise 0
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

value: if (payload.amount > 0) payload.amount else 0
Enter fullscreen mode Exit fullscreen mode

The logic reads more naturally in 2.0, but every when/otherwise in your codebase needs to be flipped — the condition and value swap positions.


3. Scoped Variables: using to do { }

DW 1.0:

items: payload map ((item) ->
  using (total = item.price * item.qty)
  {
    name: item.name,
    total: total,
    tax: total * 0.08
  }
)
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

items: payload map ((item) ->
  do {
    var total = item.price * item.qty
    ---
    {
      name: item.name,
      total: total,
      tax: total * 0.08
    }
  }
)
Enter fullscreen mode Exit fullscreen mode

The do { var ... --- body } block is more verbose but also more powerful — you can define multiple variables, functions, and even types inside a do block.


4. Type Names: Lowercase to Capitalized

Every type reference changed from lowercase with : prefix to capitalized without prefix.

DW 1.0 DW 2.0
:string String
:number Number
:boolean Boolean
:object Object
:array Array
:date Date
:datetime DateTime

DW 1.0:

payload.price as :number
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

payload.price as Number
Enter fullscreen mode Exit fullscreen mode

5. Null Handling: when/otherwise to default

DW 2.0 introduced the default operator, which replaces the verbose null-checking pattern from 1.0.

DW 1.0:

name: payload.name when payload.name != null otherwise "Unknown"
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

name: payload.name default "Unknown"
Enter fullscreen mode Exit fullscreen mode

default handles the entire null chain. If payload itself is null, payload.name default "Unknown" still returns "Unknown" without throwing an error.


6. String Interpolation (New in 2.0)

DW 1.0 only had concatenation. DW 2.0 added interpolation.

DW 1.0:

greeting: "Hello, " ++ payload.name ++ "! You have " ++ payload.count ++ " items."
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

greeting: "Hello, $(payload.name)! You have $(payload.count) items."
Enter fullscreen mode Exit fullscreen mode

Use $(expression) inside double-quoted strings. Cleaner, more readable, and less error-prone than chaining ++.


7. Date Functions: now to now()

In DW 1.0, now was a keyword. In DW 2.0, it's a function call.

DW 1.0:

timestamp: now as :string {format: "yyyy-MM-dd"}
Enter fullscreen mode Exit fullscreen mode

DW 2.0:

timestamp: now() as String {format: "yyyy-MM-dd"}
Enter fullscreen mode Exit fullscreen mode

New Features in DW 2.0

Don't just translate 1.0 syntax — take advantage of what 2.0 added:

Pattern Matching

payload.status match {
  case "ACTIVE" -> "green"
  case "PENDING" -> "yellow"
  case "INACTIVE" -> "red"
  else -> "gray"
}
Enter fullscreen mode Exit fullscreen mode

Module Imports

import * from dw::core::Strings
import * from dw::core::Arrays

---
{
  name: capitalize(payload.name),
  items: countBy(payload.items, (item) -> item.category)
}
Enter fullscreen mode Exit fullscreen mode

Period/Duration Literals

nextWeek: |2026-02-15| + |P7D|
lastMonth: now() - |P1M|
threeHours: |PT3H|
Enter fullscreen mode Exit fullscreen mode

Enhanced Type System

type Address = {street: String, city: String, zip: String}
type Customer = {name: String, address: Address}
Enter fullscreen mode Exit fullscreen mode

Common Migration Anti-Patterns

These are the mistakes I see most when teams migrate. Don't just translate syntax — fix these patterns while you're at it.

1. Translating when/otherwise for null checks instead of using default

Bad (literal translation):

// Translated from DW 1.0 — works but wasteful
name: if (payload.name != null) payload.name else "Unknown"
Enter fullscreen mode Exit fullscreen mode

Good (idiomatic DW 2.0):

name: payload.name default "Unknown"
Enter fullscreen mode Exit fullscreen mode

2. Not handling repeating XML elements

This bug existed in 1.0 too, but migration is a good time to fix it.

Bad:

// Only returns FIRST item — silent data loss
items: payload.Order.Item
Enter fullscreen mode Exit fullscreen mode

Good:

// Returns ALL items as an array
items: payload.Order.*Item
Enter fullscreen mode Exit fullscreen mode

3. Keeping reduce where simpler functions exist

DW 2.0 has richer built-in functions. Replace verbose reduce calls:

Bad:

total: payload.items reduce ((item, acc = 0) -> acc + item.price)
Enter fullscreen mode Exit fullscreen mode

Good:

total: sum(payload.items.price)
Enter fullscreen mode Exit fullscreen mode

4. Not casting CSV/XML string values

Bad:

// quantity is "5" (string), not 5 (number)
quantity: row.quantity
Enter fullscreen mode Exit fullscreen mode

Good:

quantity: row.quantity as Number
Enter fullscreen mode Exit fullscreen mode

5. Writing recursive functions without @TailRec

Bad:

fun flatten(data) = ...  // Stack overflow on deep data
Enter fullscreen mode Exit fullscreen mode

Good:

@TailRec()
fun flatten(data) = ...  // Safe for any depth
Enter fullscreen mode Exit fullscreen mode

Migration Checklist

Use this as a quick reference when converting each DW file:

  • [ ] Change %output to output, %var to var, %function to fun
  • [ ] Replace when ... otherwise with if ... else (flip condition/value order)
  • [ ] Replace null checks with default operator
  • [ ] Update type names: :string to String, :number to Number, etc.
  • [ ] Replace using (...) with do { var ... --- ... }
  • [ ] Change now to now() and other function-call syntax
  • [ ] Replace ++ concatenation with $(...) string interpolation where appropriate
  • [ ] Add as Number, as Date casts for CSV/XML fields
  • [ ] Use .*Element for repeating XML elements
  • [ ] Look for reduce calls that can use built-in functions (sum, groupBy, maxBy)
  • [ ] Add @TailRec() to any recursive functions
  • [ ] Test in the DataWeave Playground

Full Reference

This article covers the most common migration changes. For the complete guide with more examples:

Explore the full repo


Currently migrating from Mule 3 to Mule 4? What's the trickiest DW transformation you've had to convert? Share in the comments.

Top comments (0)