<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ThaSha</title>
    <description>The latest articles on DEV Community by ThaSha (@thasha).</description>
    <link>https://dev.to/thasha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3783981%2F7124d594-3898-4f92-ac8d-ec2882dc48de.png</url>
      <title>DEV Community: ThaSha</title>
      <link>https://dev.to/thasha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thasha"/>
    <language>en</language>
    <item>
      <title>RFC 7807 Error Responses in DataWeave: One Transform for All 14 Endpoints</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 12 Apr 2026 05:42:27 +0000</pubDate>
      <link>https://dev.to/thasha/rfc-7807-error-responses-in-dataweave-one-transform-for-all-14-endpoints-4el0</link>
      <guid>https://dev.to/thasha/rfc-7807-error-responses-in-dataweave-one-transform-for-all-14-endpoints-4el0</guid>
      <description>&lt;p&gt;Our API had 14 endpoints and 14 different error response formats. The mobile team spent 3 days writing separate error parsers for each one. I standardized everything to RFC 7807 with one DataWeave transform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RFC 7807 defines a standard error format: type, title, status, detail, timestamp&lt;/li&gt;
&lt;li&gt;One DataWeave error mapping lookup replaces 14 inconsistent formats&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;default&lt;/code&gt; clause on the lookup is critical — unmapped errors return null without it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;application/problem+json&lt;/code&gt; MIME type supported since Mule 4.4&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: 14 Endpoints, 14 Error Formats
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not found"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;B&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Resource missing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;C&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"issue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mobile team needed a different error parser for each endpoint. 3 days of defensive parsing just to show error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: RFC 7807 Error Envelope
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
var errorMapping = {
    "HTTP:BAD_REQUEST": { status: 400, title: "Bad Request", "type": "client-error" },
    "HTTP:UNAUTHORIZED": { status: 401, title: "Unauthorized", "type": "authentication" },
    "HTTP:NOT_FOUND": { status: 404, title: "Not Found", "type": "not-found" },
    "HTTP:TIMEOUT": { status: 504, title: "Gateway Timeout", "type": "timeout" }
}
var errorInfo = errorMapping[payload.error.errorType.identifier] default { status: 500, title: "Internal Server Error", "type": "server-error" }
---
{
  "type": "https://api.example.com/errors/$(errorInfo.'type')",
  title: "errorInfo.title,"
  status: errorInfo.status,
  detail: payload.error.description,
  timestamp: now()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every error, every endpoint, same format. The mobile team wrote 1 parser.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Error Mapping Lookup
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;errorMapping&lt;/code&gt; object maps Mule error type identifiers to HTTP semantics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var errorInfo = errorMapping[payload.error.errorType.identifier]
  default { status: 500, title: "Internal Server Error", "type": "server-error" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;errorType.identifier&lt;/code&gt; comes from Mule's error object — values like "HTTP:BAD_REQUEST", "HTTP:NOT_FOUND", "VALIDATION:INVALID_PAYLOAD".&lt;/p&gt;

&lt;p&gt;The lookup &lt;code&gt;errorMapping["HTTP:NOT_FOUND"]&lt;/code&gt; returns the mapped status and title. If the error type isn't in the mapping, &lt;code&gt;default&lt;/code&gt; provides a 500 fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap: Missing default Clause
&lt;/h2&gt;

&lt;p&gt;Without &lt;code&gt;default&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var errorInfo = errorMapping[payload.error.errorType.identifier]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unmapped error type (like "CUSTOM:BUSINESS_RULE") returns &lt;code&gt;null&lt;/code&gt;. Your response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Business rule violated"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Technically valid JSON. Completely useless to the client. The mobile app shows "null" as the error title.&lt;/p&gt;

&lt;p&gt;I hit this in production when a custom connector threw "CUSTOM:RATE_EXCEEDED" — an error type I hadn't mapped. The API returned a null-filled error response. The mobile app crashed trying to display &lt;code&gt;null.title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Always use &lt;code&gt;default&lt;/code&gt; on error mapping lookups. The default should be a valid 500 response, not null.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reserved Word Trap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;errorInfo.'type'   // Works — quoted field access
errorInfo.type     // Parse error — "type" is reserved in DataWeave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;type&lt;/code&gt; is a reserved keyword. When your error mapping uses &lt;code&gt;type&lt;/code&gt; as a field name, you must access it with quotes: &lt;code&gt;errorInfo.'type'&lt;/code&gt;. Without quotes, DataWeave throws a parse error.&lt;/p&gt;

&lt;p&gt;I spent 20 minutes on this. The error message says "unexpected token" — it doesn't mention that &lt;code&gt;type&lt;/code&gt; is reserved.&lt;/p&gt;

&lt;h2&gt;
  
  
  RFC 7807 Fields
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;URI identifying the error category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Human-readable error title&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;HTTP status code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;detail&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Specific error description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;instance&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;URI of the failing request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;When the error occurred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;correlationId&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Request trace ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every API project gets the error envelope pattern in the global error handler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default&lt;/code&gt; on every mapping lookup — no exceptions&lt;/li&gt;
&lt;li&gt;Quoted access for &lt;code&gt;type&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;I add &lt;code&gt;correlationId&lt;/code&gt; from the Mule correlation ID for trace support&lt;/li&gt;
&lt;li&gt;Content-Type: &lt;code&gt;application/problem+json&lt;/code&gt; (supported since Mule 4.4)&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DataWeave partition() for Bulk API Responses: Stop Returning Silent 200 OKs</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 21:26:44 +0000</pubDate>
      <link>https://dev.to/thasha/dataweave-partition-for-bulk-api-responses-stop-returning-silent-200-oks-44eg</link>
      <guid>https://dev.to/thasha/dataweave-partition-for-bulk-api-responses-stop-returning-silent-200-oks-44eg</guid>
      <description>&lt;p&gt;Our bulk import API returned 200 OK on every request. Nobody noticed that 40% of records were failing silently for 3 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;partition()&lt;/code&gt; from &lt;code&gt;dw::core::Arrays&lt;/code&gt; splits an array into success/failure groups in one pass&lt;/li&gt;
&lt;li&gt;Build summary (total, succeeded, failed, successRate) + per-record results + error extraction&lt;/li&gt;
&lt;li&gt;Empty batch trap: &lt;code&gt;successCount / total * 100&lt;/code&gt; divides by zero on empty arrays&lt;/li&gt;
&lt;li&gt;The caller needs to know EXACTLY what failed and why — not just "200 OK"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Silent Bulk Failures
&lt;/h2&gt;

&lt;p&gt;We had a bulk import API. Clients sent 5,000-10,000 records per batch. The API processed each record individually — some succeeded, some failed (duplicate keys, invalid formats, missing fields).&lt;/p&gt;

&lt;p&gt;The old response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Batch processed"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No per-record status. No failure count. No error details. The client had no idea which records failed. They found out 3 days later when reconciliation showed missing data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: partition() + Summary Builder
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
import partition from dw::core::Arrays
output application/json
var parts = payload partition (item) -&amp;gt; item.status == "SUCCESS"
var successCount = sizeOf(parts.success)
var total = sizeOf(payload)
---
{
  summary: {
    total: total,
    successful: successCount,
    failed: total - successCount,
    successRate: (successCount / total * 100) as String ++ "%"
  },
  results: payload map {
    id: $.id,
    status: $.status,
    (error: $.error) if $.status == "FAILED"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How partition() Works
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;partition()&lt;/code&gt; takes an array and a predicate. Returns an object with two keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;success&lt;/code&gt;: all items where the predicate returned true&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failure&lt;/code&gt;: all items where it returned false
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import partition from dw::core::Arrays
---
[1, 2, 3, 4, 5] partition (n) -&amp;gt; n &amp;gt; 3
// {success: [4, 5], failure: [1, 2, 3]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One pass. No need to filter twice. Cleaner than separate &lt;code&gt;filter&lt;/code&gt; calls for success and failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conditional Key Trick
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;results: payload map {
  id: $.id,
  status: $.status,
  (error: $.error) if $.status == "FAILED"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;(error: $.error) if $.status == "FAILED"&lt;/code&gt; syntax conditionally includes the error field. Successful records have no &lt;code&gt;error&lt;/code&gt; key — it's absent, not null. Clean API response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap: Divide by Zero on Empty Batch
&lt;/h2&gt;

&lt;p&gt;A health check sent an empty array to our bulk endpoint. &lt;code&gt;total = 0&lt;/code&gt;. The successRate calculation: &lt;code&gt;0 / 0 * 100&lt;/code&gt;. Runtime error: "Cannot divide by zero."&lt;/p&gt;

&lt;p&gt;CloudHub showed a 500 error. The health check saw "API is down." The API was fine — it just crashed on empty input.&lt;/p&gt;

&lt;p&gt;The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;successRate: if (total &amp;gt; 0) ((successCount / total * 100) as String ++ "%") else "N/A"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One guard. I now add this to every bulk response builder. Empty batches are valid input — your API should handle them gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Caller Gets
&lt;/h2&gt;

&lt;p&gt;Before (useless):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Batch processed"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (actionable):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"successful"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"successRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"60.0%"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAILED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Duplicate key"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"R5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAILED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid format"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller knows: 60% success rate. R2 failed on duplicate key. R5 failed on format. They can retry just the 2 failures instead of resending all 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;p&gt;Every bulk API I build includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;partition()&lt;/code&gt; for success/failure split&lt;/li&gt;
&lt;li&gt;Summary with counts + percentage&lt;/li&gt;
&lt;li&gt;Per-record results with conditional error field&lt;/li&gt;
&lt;li&gt;Empty-batch guard on all division operations&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;errors&lt;/code&gt; array as a convenience extraction for clients that only want failures&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Parsing LLM Responses in DataWeave: 3 Layers of Defense Against Markdown Fences</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 20:12:23 +0000</pubDate>
      <link>https://dev.to/thasha/parsing-llm-responses-in-dataweave-3-layers-of-defense-against-markdown-fences-4ch5</link>
      <guid>https://dev.to/thasha/parsing-llm-responses-in-dataweave-3-layers-of-defense-against-markdown-fences-4ch5</guid>
      <description>&lt;p&gt;I connected MuleSoft to GPT-4o last quarter for a support ticket classifier. The prompt builder worked (I covered that in a previous post). The LLM call worked. Then the response came back and my parser crashed.&lt;/p&gt;

&lt;p&gt;The LLM was supposed to return clean JSON. Instead it returned: "Here is my analysis:\n&lt;br&gt;
&lt;br&gt;
&lt;code&gt;json\n{\"ranking\": [\"TK-101\"]}\n&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
\nLet me know if you need anything else."&lt;/p&gt;

&lt;p&gt;Markdown fences. Preamble text. Trailing conversation. My &lt;code&gt;read()&lt;/code&gt; call choked on "Here is my analysis" — not valid JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLMs wrap JSON responses in markdown fences — your parser must extract the JSON first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;try()&lt;/code&gt; from &lt;code&gt;dw::Runtime&lt;/code&gt; catches parse failures gracefully — without it, one malformed response crashes your flow&lt;/li&gt;
&lt;li&gt;Always validate required keys after parsing — LLMs hallucinate extra fields and omit required ones&lt;/li&gt;
&lt;li&gt;The regex for fence extraction: &lt;code&gt;raw match /(?s)&lt;/code&gt;&lt;code&gt;(?:json)?\s*(\{.*?\})\s*&lt;/code&gt;&lt;code&gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test with 5 response variants: clean JSON, fenced JSON, fenced without language tag, no fences, broken JSON&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Problem: LLMs Don't Return Clean JSON
&lt;/h2&gt;

&lt;p&gt;You ask GPT-4o to return JSON. Sometimes it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ranking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TK-101"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TK-098"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Timeout is critical."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But often it adds commentary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;analysis:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
json&lt;br&gt;
{"ranking": ["TK-101"], "summary": "Timeout is critical."}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let me know if you need anything else.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;/p&gt;

&lt;p&gt;That's not valid JSON. &lt;code&gt;read(raw, "application/json")&lt;/code&gt; throws. Your Mule flow crashes. 50,000 LLM responses per day = 50,000 potential crashes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution: 3-Layer Defense in DataWeave
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
import try from dw::Runtime
output application/json
var raw = payload.rawResponse
var fenceMatch = raw match /(?s)```

(?:json)?\s*(\{.*?\})\s*

```/
var jsonStr = if (fenceMatch[1]?) fenceMatch[1] else raw
var parsed = try(() -&amp;gt; read(jsonStr, "application/json"))
var keys = if (parsed.success) (parsed.result pluck $$) else []
var missing = payload.requiredKeys filter (k) -&amp;gt; !(keys contains k)
---
{
  parsed: if (parsed.success) parsed.result else null,
  valid: parsed.success and isEmpty(missing),
  missingKeys: missing
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Layer 1: Regex Fence Extraction
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var fenceMatch = raw match /(?s)```

(?:json)?\s*(\{.*?\})\s*

```/
var jsonStr = if (fenceMatch[1]?) fenceMatch[1] else raw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The regex matches content between markdown fences. &lt;code&gt;(?s)&lt;/code&gt; enables dotall mode so &lt;code&gt;.&lt;/code&gt; matches newlines. &lt;code&gt;(?:json)?&lt;/code&gt; handles both&lt;br&gt;
&lt;br&gt;
 `&lt;code&gt;json` and bare&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
``&lt;code&gt;. The captured group&lt;/code&gt;({.*?})` extracts just the JSON object.&lt;/p&gt;

&lt;p&gt;If no fences found (&lt;code&gt;fenceMatch[1]?&lt;/code&gt; is false), fall back to parsing the raw string directly. This handles the case where the LLM returns clean JSON without fences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: try() Wraps the Parse
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
dataweave
var parsed = try(() -&amp;gt; read(jsonStr, "application/json"))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;try()&lt;/code&gt; from &lt;code&gt;dw::Runtime&lt;/code&gt; is the critical piece. Without it, &lt;code&gt;read()&lt;/code&gt; on invalid JSON throws an exception and crashes the flow. With &lt;code&gt;try()&lt;/code&gt;, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Success: &lt;code&gt;{success: true, result: {...parsed object...}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Failure: &lt;code&gt;{success: false, error: {...error details...}}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your flow continues either way. Log the error, route to a dead-letter queue, retry with a different prompt — your choice. But the flow never crashes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trap:&lt;/strong&gt; If you don't check &lt;code&gt;parsed.success&lt;/code&gt; before accessing &lt;code&gt;parsed.result&lt;/code&gt;, you get &lt;code&gt;null&lt;/code&gt; on failure. Downstream code that expects an object gets null instead. Always check the success flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Required Key Validation
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
dataweave
var keys = if (parsed.success) (parsed.result pluck $$) else []
var missing = payload.requiredKeys filter (k) -&amp;gt; !(keys contains k)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;LLMs hallucinate. You asked for &lt;code&gt;ranking&lt;/code&gt; and &lt;code&gt;summary&lt;/code&gt;. The LLM returned &lt;code&gt;ranking&lt;/code&gt;, &lt;code&gt;analysis&lt;/code&gt;, and &lt;code&gt;confidence&lt;/code&gt; — but not &lt;code&gt;summary&lt;/code&gt;. The JSON is valid but missing a required field.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pluck $$&lt;/code&gt; extracts all keys from the parsed object. &lt;code&gt;filter&lt;/code&gt; checks each required key against the actual keys. &lt;code&gt;missing&lt;/code&gt; tells you exactly which keys the LLM omitted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 Response Variants to Test
&lt;/h2&gt;

&lt;p&gt;I test every LLM parser against these variants:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;What Breaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clean JSON&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"ranking": [...], "summary": "..."}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing — baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fenced with language&lt;/td&gt;
&lt;td&gt;```&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
&lt;code&gt;json\n{...}\n&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
&lt;code&gt;| `read()` without fence extraction |&lt;br&gt;
| Fenced without language |&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
&lt;code&gt;\n{...}\n&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
``&lt;code&gt;| Regex that requires&lt;/code&gt;json&lt;code&gt;after fence |&lt;br&gt;
| No fences, with preamble |&lt;/code&gt;Here is my analysis: {"ranking": [...]}&lt;code&gt;| Naive&lt;/code&gt;read()&lt;code&gt;on full string |&lt;br&gt;
| Broken JSON |&lt;/code&gt;{"ranking": ["TK-101"&lt;code&gt;(truncated) | Any parser without&lt;/code&gt;try()` |&lt;/p&gt;

&lt;p&gt;I hit all 5 in production within the first week. The same model, same prompt, same temperature — 5 different formats across 50,000 responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap: The Regex Doesn't Handle Nested Objects Well
&lt;/h2&gt;

&lt;p&gt;The regex &lt;code&gt;\{.*?\}&lt;/code&gt; uses non-greedy matching. It works for flat JSON objects. But if your LLM returns nested objects:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
json
{"analyses": [{"ticketId": "TK-101", "action": "increase pool"}], "summary": "..."}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The non-greedy &lt;code&gt;.*?&lt;/code&gt; stops at the first &lt;code&gt;}&lt;/code&gt; — inside the nested array. You get a partial JSON parse.&lt;/p&gt;

&lt;p&gt;For production, I switched to finding the matching closing brace by counting open/close braces, not regex. But the regex works for 90% of LLM responses where the top-level structure is flat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;The parser handles 50,000 LLM responses per day. Average parse time: 2ms per response. The regex is the slowest part — &lt;code&gt;try()&lt;/code&gt; and &lt;code&gt;read()&lt;/code&gt; are fast because they're native DataWeave functions.&lt;/p&gt;

&lt;p&gt;Zero flow crashes since deploying the 3-layer approach. Before it, we had 3-5 crashes per day from malformed LLM responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every LLM integration gets this parser as the first step after the API call&lt;/li&gt;
&lt;li&gt;Failed parses go to a dead-letter queue with the raw response for debugging&lt;/li&gt;
&lt;li&gt;Missing key reports go to a monitoring dashboard — tracks LLM reliability over time&lt;/li&gt;
&lt;li&gt;I test with all 5 variants before any production deployment&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building LLM Prompts From Enterprise Data in DataWeave: 2 Traps That Garbled My AI Output</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 18:53:50 +0000</pubDate>
      <link>https://dev.to/thasha/building-llm-prompts-from-enterprise-data-in-dataweave-2-traps-that-garbled-my-ai-output-41f8</link>
      <guid>https://dev.to/thasha/building-llm-prompts-from-enterprise-data-in-dataweave-2-traps-that-garbled-my-ai-output-41f8</guid>
      <description>&lt;p&gt;I connected a MuleSoft API to an LLM last quarter for a support ticket classifier. The API call was easy — the MuleSoft AI Connector handles that. Building the prompt payload from enterprise data? That's where I spent 2 hours debugging escape sequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DataWeave transforms ticket data into structured LLM prompt payloads (system + user roles)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;joinBy "\n"&lt;/code&gt; produces literal backslash-n in JSON — not actual newlines. The LLM sees one continuous line.&lt;/li&gt;
&lt;li&gt;No token estimation → prompt consumes most of the context window → truncated response&lt;/li&gt;
&lt;li&gt;The pattern builds system role, user role, model config, and structured response format in 12 lines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Pattern: Enterprise Data to LLM Prompt
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
var systemPrompt = "You are an enterprise support analyst."
var lines = payload.ticketHistory map (t) -&amp;gt; "- [$(t.priority upper)] $(t.id): $(t.subject)"
var userPrompt = "Analyze tickets for $(payload.customer.name):\n" ++ (lines joinBy "\n")
---
{
  model: payload.model,
  max_tokens: payload.maxTokens,
  messages: [
    {role: "system", content: systemPrompt},
    {role: "user", content: userPrompt}
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Input: customer object + ticket array + model config.&lt;br&gt;
Output: ready-to-send LLM payload with system instructions and contextual user prompt.&lt;/p&gt;



&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Trap 1: joinBy "\n" Is Literal, Not a Newline
&lt;/h2&gt;

&lt;p&gt;The prompt looks correct in the DataWeave Playground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- [HIGH] TK-101: API timeout
- [MEDIUM] TK-098: OAuth refresh failing
- [LOW] TK-095: Batch stuck at 80 pct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the actual JSON sent to the LLM contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"- [HIGH] TK-101: API timeout&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- [MEDIUM] TK-098: OAuth refresh failing&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- [LOW] TK-095: Batch stuck at 80 pct"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Literal &lt;code&gt;\n&lt;/code&gt; characters. Not newlines. The LLM sees one continuous string and its analysis is garbled — it can't distinguish between tickets.&lt;/p&gt;

&lt;p&gt;I spent 2 hours wondering why the classification was wrong before I checked the raw HTTP request body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Use an actual newline in a variable, or use template literals that preserve whitespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: No Token Estimation
&lt;/h2&gt;

&lt;p&gt;I injected 200 ticket summaries into one prompt. Each summary is ~20 tokens. That's 4,000 tokens just for the ticket list. max_tokens was set to 500 for the response.&lt;/p&gt;

&lt;p&gt;The model's context window was 4,096 tokens. Prompt + response budget = 4,500 tokens. Didn't fit. The response was truncated mid-sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Estimate prompt tokens before setting max_tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var estimatedPromptTokens = sizeOf(userPrompt) / 4
var safeMaxTokens = 4096 - estimatedPromptTokens - 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Use This Pattern
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;th&gt;Alternatives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MuleSoft AI Connector integration&lt;/td&gt;
&lt;td&gt;Direct API call with HTTP requester&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured enterprise data → LLM prompt&lt;/td&gt;
&lt;td&gt;Hardcoded prompts (won't scale)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic context injection (tickets, customer data)&lt;/td&gt;
&lt;td&gt;Static system prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple LLM providers (swap model field)&lt;/td&gt;
&lt;td&gt;Provider-specific SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DataWeave Runtime Type Detection: Auto-Discover Schema Mismatches Before They Break Production</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 17:37:34 +0000</pubDate>
      <link>https://dev.to/thasha/dataweave-runtime-type-detection-auto-discover-schema-mismatches-before-they-break-production-3768</link>
      <guid>https://dev.to/thasha/dataweave-runtime-type-detection-auto-discover-schema-mismatches-before-they-break-production-3768</guid>
      <description>&lt;p&gt;I built a schema discovery function for a 6-system integration last year. Each source sent different types for the same fields. One function auto-detects types at runtime and builds a complete schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;describeType&lt;/code&gt; function uses &lt;code&gt;is&lt;/code&gt; operator chain to detect String, Number, Boolean, Array, Object, Null at runtime&lt;/li&gt;
&lt;li&gt;Run against sample records from each source to discover type mismatches before writing transforms&lt;/li&gt;
&lt;li&gt;Order of &lt;code&gt;is&lt;/code&gt; checks matters — always check Array before Object&lt;/li&gt;
&lt;li&gt;Null handling needs explicit check — don't rely on catch-all else&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: 6 Sources, 6 Different Types
&lt;/h2&gt;

&lt;p&gt;We integrated with 6 systems. All sent the same logical data — customer fields. But the actual types varied:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Source A&lt;/th&gt;
&lt;th&gt;Source B&lt;/th&gt;
&lt;th&gt;Source C&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;age&lt;/td&gt;
&lt;td&gt;Number &lt;code&gt;30&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;String &lt;code&gt;"30"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;active&lt;/td&gt;
&lt;td&gt;Boolean &lt;code&gt;true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;String &lt;code&gt;"true"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Number &lt;code&gt;1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tags&lt;/td&gt;
&lt;td&gt;Array &lt;code&gt;["admin"]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;String &lt;code&gt;"admin"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Writing transforms that assumed consistent types would silently produce wrong output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Runtime Type Detection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
fun describeType(value) =
  if (value is String) "String"
  else if (value is Number) "Number"
  else if (value is Boolean) "Boolean"
  else if (value is Array) "Array"
  else if (value is Object) "Object"
  else if (value is Null) "Null"
  else "Unknown"
---
{schema: payload.fields map (f) -&amp;gt; ({
  key: f.key,
  type: describeType(f.value),
  nullable: f.value is Null,
  (example: f.value) if (f.value is String or f.value is Number),
  (itemCount: sizeOf(f.value)) if (f.value is Array)
})}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed it any payload. Get back a schema with field names, detected types, nullability, and examples.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap 1: Order of &lt;code&gt;is&lt;/code&gt; Checks Matters
&lt;/h2&gt;

&lt;p&gt;Check Array BEFORE Object. In some DataWeave versions, &lt;code&gt;[] is Object&lt;/code&gt; can return true before the Array check runs. Your schema shows "Object" for fields that are actually Arrays.&lt;/p&gt;

&lt;p&gt;I hit this in production — an array field got classified as Object. The downstream parser expected an Array, got the string "Object", and silently skipped the field. 200 records processed with missing tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Most specific types first: Null → Boolean → Number → String → Array → Object → Unknown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: Null Falls Through
&lt;/h2&gt;

&lt;p&gt;If your catch-all &lt;code&gt;else&lt;/code&gt; returns "Unknown", null values get classified as "Unknown" instead of "Null". Always check &lt;code&gt;is Null&lt;/code&gt; explicitly — don't rely on the else clause.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Preflight Process
&lt;/h2&gt;

&lt;p&gt;I run this on every new integration now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sample 100 records&lt;/strong&gt; from each source system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run describeType&lt;/strong&gt; on every field&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare schemas&lt;/strong&gt; across sources — flag any field where types differ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write transforms&lt;/strong&gt; with explicit type handling for mismatched fields&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the 6-system project, this found 4 fields with type mismatches across 2 sources. Saved 3 days of debugging that would have happened after deployment.&lt;/p&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>tutorial</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Recursive PII Masking in DataWeave: One Function for Any Depth (and the Null Trap)</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 16:20:33 +0000</pubDate>
      <link>https://dev.to/thasha/recursive-pii-masking-in-dataweave-one-function-for-any-depth-and-the-null-trap-3dka</link>
      <guid>https://dev.to/thasha/recursive-pii-masking-in-dataweave-one-function-for-any-depth-and-the-null-trap-3dka</guid>
      <description>&lt;p&gt;A compliance audit found SSN values 4 levels deep in our API responses last year. One recursive function masks everything. Then null values in production crashed 400 responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive &lt;code&gt;maskPII&lt;/code&gt; function dispatches on type: Object → check fields, Array → recurse, Primitive → pass through&lt;/li&gt;
&lt;li&gt;Works at any nesting depth with one function call: &lt;code&gt;maskPII(payload)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Null values crash it — add explicit null handling before the Object case&lt;/li&gt;
&lt;li&gt;No hardcoded paths needed — the function finds PII fields at any level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: PII at Unknown Depth
&lt;/h2&gt;

&lt;p&gt;Our org chart API returns hierarchical data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ceo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice Chen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ssn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123-45-6789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@acme.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob Martinez"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ssn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"234-56-7890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob@acme.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Carol Nguyen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ssn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"345-67-8901"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSN at level 1 (CEO), level 2 (VP), level 3 (Director). The compliance requirement: mask ALL of them. The depth varies per org — some go 6 levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Recursive Solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
fun maskSsn(s: String): String = "***-**-" ++ s[-4 to -1]
fun maskEmail(e: String): String = do { var parts = e splitBy "@" --- parts[0][0] ++ "****@" ++ parts[1] }
fun maskPII(data: Any): Any =
    data match {
        case obj is Object -&amp;gt; obj mapObject (value, key) -&amp;gt;
            if ((key as String) == "ssn") {(key): maskSsn(value as String)}
            else if ((key as String) == "email") {(key): maskEmail(value as String)}
            else {(key): maskPII(value)}
        case arr is Array -&amp;gt; arr map maskPII($)
        else -&amp;gt; data
    }
---
maskPII(payload)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type dispatch: Objects get field-level checking. Arrays recurse into each element. Strings, numbers, booleans pass through unchanged.&lt;/p&gt;

&lt;p&gt;One call — &lt;code&gt;maskPII(payload)&lt;/code&gt; — handles any depth.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Null Trap
&lt;/h2&gt;

&lt;p&gt;Production payloads had null values at level 3 — a manager with no email. The &lt;code&gt;data match&lt;/code&gt; block dispatched null to... somewhere unexpected. It tried &lt;code&gt;mapObject&lt;/code&gt; on null. Crash. 400 API responses failed.&lt;/p&gt;

&lt;p&gt;The fix: add explicit null handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun maskPII(data: Any): Any =
    data match {
        case is Null -&amp;gt; null
        case obj is Object -&amp;gt; obj mapObject ...
        case arr is Array -&amp;gt; arr map maskPII($)
        else -&amp;gt; data
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;case is Null -&amp;gt; null&lt;/code&gt; catches null before it reaches the Object handler. Now null passes through unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Recursive Functions
&lt;/h2&gt;

&lt;p&gt;I test with these edge cases now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Empty object &lt;code&gt;{}&lt;/code&gt; at each level&lt;/li&gt;
&lt;li&gt;Empty array &lt;code&gt;[]&lt;/code&gt; at each level&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; at each level&lt;/li&gt;
&lt;li&gt;Mixed types in arrays: &lt;code&gt;["text", 42, null, {"key": "value"}]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Maximum expected depth (6 levels for our org chart)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;5 minutes of setup in the DataWeave Playground. Prevented every recursive bug since.&lt;/p&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DataWeave match/case: Replace If/Else Chains and Avoid the Missing Else Crash</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 15:02:08 +0000</pubDate>
      <link>https://dev.to/thasha/dataweave-matchcase-replace-ifelse-chains-and-avoid-the-missing-else-crash-18md</link>
      <guid>https://dev.to/thasha/dataweave-matchcase-replace-ifelse-chains-and-avoid-the-missing-else-crash-18md</guid>
      <description>&lt;p&gt;I replaced a 40-line if/else chain with 4 match/case lines last month. Then I forgot the else clause and 3,000 production events backed up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;match/case&lt;/code&gt; replaces nested if/else with value matching, guard conditions, and type dispatch&lt;/li&gt;
&lt;li&gt;Missing &lt;code&gt;else&lt;/code&gt; clause causes runtime errors on unmatched values — unlike switch/default, there's no implicit fallthrough&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event.type&lt;/code&gt; fails — &lt;code&gt;type&lt;/code&gt; is a reserved word in DataWeave, use &lt;code&gt;event."type"&lt;/code&gt; with quotes&lt;/li&gt;
&lt;li&gt;Guard conditions (&lt;code&gt;case t if t startsWith "payment."&lt;/code&gt;) handle wildcard matching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Growing If/Else Chains
&lt;/h2&gt;

&lt;p&gt;We had an event routing flow. Orders, payments, user actions — each type needed a different processing queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (event."type" == "order.created") { category: "ORDER", action: "PROCESS" }
else if (event."type" == "order.cancelled") { category: "ORDER", action: "REFUND" }
else if (event."type" == "payment.received") { category: "PAYMENT", action: "RECONCILE" }
else if (event."type" == "payment.refunded") { category: "PAYMENT", action: "RECONCILE" }
else if (event."type" == "user.login") { category: "CUSTOMER", action: "SYNC" }
else if (event."type" == "user.logout") { category: "CUSTOMER", action: "SYNC" }
else if (event."type" == "user.signup") { category: "CUSTOMER", action: "ONBOARD" }
else { category: "SYSTEM", action: "LOG" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;40 lines. Growing every sprint. Hard to read. Repetitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: match/case
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
fun routeEvent(event: Object): Object =
    event."type" match {
        case "order.created" -&amp;gt; ({ category: "ORDER", action: "PROCESS", priority: "NORMAL" })
        case "order.cancelled" -&amp;gt; ({ category: "ORDER", action: "REFUND", priority: "HIGH" })
        case eventType if eventType startsWith "payment." -&amp;gt; ({ category: "PAYMENT", action: "RECONCILE", priority: "HIGH" })
        else -&amp;gt; ({ category: "SYSTEM", action: "LOG", priority: "LOW" })
    }
---
payload.events map (event) -&amp;gt; routeEvent(event)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4 cases. Value matching for exact types. Guard condition for the payment prefix. Else for everything unexpected.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap 1: The Missing Else Clause
&lt;/h2&gt;

&lt;p&gt;I deployed my first version without &lt;code&gt;else&lt;/code&gt;. For 3 weeks, every event was a known type. Then an "inventory.updated" event arrived — a type I hadn't handled.&lt;/p&gt;

&lt;p&gt;Runtime error. 3,000 events backed up in the queue before monitoring caught it.&lt;/p&gt;

&lt;p&gt;DataWeave's match/case does NOT have implicit fallthrough. Without &lt;code&gt;else&lt;/code&gt;, an unmatched value throws an exception. Unlike JavaScript's switch where falling through is the default, DataWeave requires explicit handling of every possible value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule: every match block needs an else clause. No exceptions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: Reserved Word Field Access
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event.type    // PARSE ERROR — "type" is a reserved word
event."type"  // works — quoted field name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DataWeave reserves &lt;code&gt;type&lt;/code&gt; as a keyword. When your JSON has a &lt;code&gt;type&lt;/code&gt; field (which is extremely common in event-driven architectures), you must quote it in the selector. I spent 20 minutes debugging this the first time.&lt;/p&gt;

&lt;p&gt;Other reserved words that break field selectors: &lt;code&gt;match&lt;/code&gt;, &lt;code&gt;case&lt;/code&gt;, &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, &lt;code&gt;do&lt;/code&gt;, &lt;code&gt;fun&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard Conditions: Pattern Matching with Logic
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;case eventType if eventType startsWith "payment."&lt;/code&gt; syntax combines pattern matching with boolean guards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event."type" match {
    case t if t startsWith "order." -&amp;gt; handleOrder(t)
    case t if t matches /user\.(.*)/ -&amp;gt; handleUser(t)
    case t if sizeOf(t) &amp;gt; 50 -&amp;gt; handleLongType(t)
    else -&amp;gt; handleUnknown()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use any boolean expression after &lt;code&gt;if&lt;/code&gt;. The bound variable &lt;code&gt;t&lt;/code&gt; holds the matched value for use in both the guard and the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every match block has an else clause before deployment&lt;/li&gt;
&lt;li&gt;Reserved word fields always use quoted selectors&lt;/li&gt;
&lt;li&gt;New event types go into the match block proactively — I review dead-letter queue weekly&lt;/li&gt;
&lt;li&gt;Code reviews flag match without else as a blocking issue&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>tutorial</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Multi-Level GroupBy in DataWeave: 2 Traps That Break Your Nested Aggregations</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 13:42:40 +0000</pubDate>
      <link>https://dev.to/thasha/multi-level-groupby-in-dataweave-2-traps-that-break-your-nested-aggregations-2944</link>
      <guid>https://dev.to/thasha/multi-level-groupby-in-dataweave-2-traps-that-break-your-nested-aggregations-2944</guid>
      <description>&lt;p&gt;I spent 2 days building a 3-level sales report in DataWeave. Region to country to product, with revenue totals at every level. The code was 10 lines. The debugging was 2 days. Two traps that nobody warns you about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nested &lt;code&gt;groupBy&lt;/code&gt; creates hierarchical structures from flat data — 3 levels in 10 lines&lt;/li&gt;
&lt;li&gt;Every &lt;code&gt;groupBy&lt;/code&gt; returns an Object — you MUST use &lt;code&gt;mapObject&lt;/code&gt; at every level, never &lt;code&gt;map&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sum()&lt;/code&gt; on an empty group returns &lt;code&gt;null&lt;/code&gt; not &lt;code&gt;0&lt;/code&gt; — your dashboard shows "null" for revenue&lt;/li&gt;
&lt;li&gt;Handles 50,000 records in under 2 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Flat Data to Tree Structure
&lt;/h2&gt;

&lt;p&gt;We had 50,000 order records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"North America"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"North America"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Germany"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard needed a tree: Region → Country → Product → totals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-Line Solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
---
payload groupBy $.region mapObject (regionItems, region) -&amp;gt; ({
    (region): regionItems groupBy $.country mapObject (countryItems, country) -&amp;gt; ({
        (country): countryItems groupBy $.product mapObject (productItems, product) -&amp;gt; ({
            (product): {
                totalRevenue: sum(productItems.revenue),
                count: sizeOf(productItems)
            })
        })
    })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap 1: Cannot Coerce Object to Array
&lt;/h2&gt;

&lt;p&gt;My first version used &lt;code&gt;map&lt;/code&gt; at level 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;regionItems groupBy $.country map (countryItems) -&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error: &lt;strong&gt;"Cannot coerce :object to :array"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;groupBy&lt;/code&gt; ALWAYS returns an Object. &lt;code&gt;map&lt;/code&gt; expects an Array. This error appears at EVERY level where you use &lt;code&gt;map&lt;/code&gt; instead of &lt;code&gt;mapObject&lt;/code&gt;. I hit it 3 times — once at each level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; After &lt;code&gt;groupBy&lt;/code&gt;, always &lt;code&gt;mapObject&lt;/code&gt;. Never &lt;code&gt;map&lt;/code&gt;. Three levels = three &lt;code&gt;mapObject&lt;/code&gt; calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: sum() Returns Null on Empty Groups
&lt;/h2&gt;

&lt;p&gt;A region had no records for one product category. &lt;code&gt;sum([])&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt;, not &lt;code&gt;0&lt;/code&gt;. The dashboard showed "null" in the revenue column.&lt;/p&gt;

&lt;p&gt;The finance team reported it as a data quality issue. The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;totalRevenue: sum(productItems.revenue) default 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;default 0&lt;/code&gt; to every aggregation function. &lt;code&gt;sizeOf&lt;/code&gt; correctly returns 0 for empty arrays, but &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;avg&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt; all return &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;I tested with 50,000 records across 8 regions, 23 countries, and 400 products. Processing time: under 2 seconds on a 0.1 vCore CloudHub worker.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;groupBy&lt;/code&gt; is O(n) and the data set shrinks at each level. The total is still O(n), not O(n^3). The 10-line DataWeave replaced a 200-line Java implementation that took 3 developers a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Multi-Level GroupBy
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;th&gt;Don't use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Building hierarchical reports from flat data&lt;/td&gt;
&lt;td&gt;Data is already nested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard APIs needing tree structures&lt;/td&gt;
&lt;td&gt;Output needs to stay flat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pivot-table style aggregation&lt;/td&gt;
&lt;td&gt;Only one grouping dimension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3+ dimensions of analysis&lt;/td&gt;
&lt;td&gt;Simple count/sum without hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>tutorial</category>
      <category>debugging</category>
    </item>
    <item>
      <title>The DataWeave &lt;~ Operator: How Angle Brackets in Customer Notes Broke 200 Orders</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 12:29:41 +0000</pubDate>
      <link>https://dev.to/thasha/the-dataweave-operator-how-angle-brackets-in-customer-notes-broke-200-orders-2b0j</link>
      <guid>https://dev.to/thasha/the-dataweave-operator-how-angle-brackets-in-customer-notes-broke-200-orders-2b0j</guid>
      <description>&lt;p&gt;A customer typed "" in their notes field. It broke 200 XML orders and I spent 3 hours finding a one-line fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User-generated text with angle brackets (&lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;) breaks XML output — the parser treats them as tags&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;~&lt;/code&gt; metadata operator with &lt;code&gt;{cdata: true}&lt;/code&gt; wraps content in CDATA sections&lt;/li&gt;
&lt;li&gt;CDATA preserves angle brackets as literal text: &lt;code&gt;&amp;lt;![CDATA[text with &amp;lt;VIP&amp;gt;]]&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;~&lt;/code&gt; operator requires DataWeave 2.5 (Mule 4.5+)&lt;/li&gt;
&lt;li&gt;JSON output silently ignores cdata metadata — no error, no effect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Valid JSON, Invalid XML
&lt;/h2&gt;

&lt;p&gt;We had a Mule flow converting customer orders from JSON to XML for an ERP. Worked for 6 months. Then 200 orders got rejected.&lt;/p&gt;

&lt;p&gt;The customer's notes field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice Chen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Preferred customer since 2020 &amp;lt;VIP&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-12345"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DataWeave transform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/xml
---
order @(id: payload.orderId): {
    customer @(class: "Person", source: "CRM"): {
        name: payload.customer.name,
        email: payload.customer.email,
        notes: payload.customer.notes
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output XML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;order&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ORD-12345"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;customer&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"Person"&lt;/span&gt; &lt;span class="na"&gt;source=&lt;/span&gt;&lt;span class="s"&gt;"CRM"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Alice Chen&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;notes&amp;gt;&lt;/span&gt;Preferred customer since 2020 &lt;span class="nt"&gt;&amp;lt;VIP&amp;gt;&amp;lt;/notes&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/customer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/order&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;VIP&amp;gt;&lt;/code&gt; in the notes became an XML opening tag. No closing &lt;code&gt;&amp;lt;/VIP&amp;gt;&lt;/code&gt; tag. Invalid XML. The ERP rejected the entire batch.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: The &amp;lt;~ Metadata Operator
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/xml
---
order @(id: payload.orderId): {
    customer @(class: "Person", source: "CRM"): {
        name: payload.customer.name,
        email: payload.customer.email,
        notes: payload.customer.notes &amp;lt;~ {cdata: true}
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;order&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ORD-12345"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;customer&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"Person"&lt;/span&gt; &lt;span class="na"&gt;source=&lt;/span&gt;&lt;span class="s"&gt;"CRM"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Alice Chen&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;notes&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[Preferred customer since 2020 &amp;lt;VIP&amp;gt;]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/notes&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/customer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/order&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;~&lt;/code&gt; operator attaches metadata to a value without changing its type. &lt;code&gt;{cdata: true}&lt;/code&gt; tells the XML writer to wrap the content in a CDATA section. Angle brackets preserved. Valid XML.&lt;/p&gt;

&lt;h2&gt;
  
  
  The @() Syntax for XML Attributes
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;@()&lt;/code&gt; syntax adds XML attributes to elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer @(class: "Person", source: "CRM"): { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Produces: &lt;code&gt;&amp;lt;customer class="Person" source="CRM"&amp;gt;&lt;/code&gt;. Attributes come from your data, not hardcoded in the schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 1: &amp;lt;~ Only Works for XML Output
&lt;/h2&gt;

&lt;p&gt;I tried using &lt;code&gt;&amp;lt;~ {cdata: true}&lt;/code&gt; on a JSON API response. Nothing happened. No error. No warning. The output was identical with and without the &lt;code&gt;&amp;lt;~&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The cdata metadata is only read by the XML writer. JSON, CSV, and other formats ignore it silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: Requires DataWeave 2.5
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;~&lt;/code&gt; operator was introduced in DataWeave 2.5 (Mule 4.5+). On older runtimes, it throws a parse error.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;&amp;lt;~&lt;/code&gt;, the approach was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notes: payload.customer.notes as String {class: "cdata"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works on older runtimes but also coerces the type. &lt;code&gt;&amp;lt;~&lt;/code&gt; is cleaner because it only attaches metadata — the value type stays unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;p&gt;Any field going to XML that could contain user-generated content gets &lt;code&gt;&amp;lt;~ {cdata: true}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer notes&lt;/li&gt;
&lt;li&gt;Product descriptions&lt;/li&gt;
&lt;li&gt;Address fields (street addresses with unit symbols like #)&lt;/li&gt;
&lt;li&gt;Comment fields from ticketing systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also run a pre-transformation check: scan text fields for &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;. If found, log a warning and ensure CDATA wrapping is active.&lt;/p&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>xml</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DataWeave 2.5 Generics: How Call-Site Type Parameters Caught 3 Production Bugs</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 11:12:42 +0000</pubDate>
      <link>https://dev.to/thasha/dataweave-25-generics-how-call-site-type-parameters-caught-3-production-bugs-pkf</link>
      <guid>https://dev.to/thasha/dataweave-25-generics-how-call-site-type-parameters-caught-3-production-bugs-pkf</guid>
      <description>&lt;p&gt;I maintained a shared DataWeave utility library across 12 Mule apps for 2 years. Functions like &lt;code&gt;topN&lt;/code&gt;, &lt;code&gt;pipe&lt;/code&gt;, and &lt;code&gt;safeGet&lt;/code&gt;. All untyped. All accepting &lt;code&gt;Any&lt;/code&gt;. All silently producing wrong output when called with wrong types.&lt;/p&gt;

&lt;p&gt;Last quarter I rewrote them with DataWeave 2.5 call-site generics. Three production bugs surfaced on the first compile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DataWeave 2.5 adds Java/TypeScript-style call-site type parameters: &lt;code&gt;fun topN&amp;lt;T&amp;gt;(...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The compiler validates T at every call site — type mismatches become compile errors, not runtime surprises&lt;/li&gt;
&lt;li&gt;Requires Mule 4.5+ — older runtimes throw parse errors on &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; syntax&lt;/li&gt;
&lt;li&gt;I caught 3 bugs that had been producing wrong output for 4 months&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: Untyped Utility Functions
&lt;/h2&gt;

&lt;p&gt;My library had functions like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun topN(items, n, comp) = (items orderBy -comp($))[0 to (n - 1)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Takes anything. Returns anything. Works on Arrays of Numbers, Strings, Objects. But there's no type checking at the call site.&lt;/p&gt;

&lt;p&gt;If someone calls &lt;code&gt;topN(arrayOfStrings, 3, (s) -&amp;gt; s)&lt;/code&gt; expecting numeric sorting, they get alphabetical sorting. No error. No warning. Wrong data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Call-Site Generics
&lt;/h2&gt;

&lt;p&gt;DataWeave 2.5 introduced type parameters at the call site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun topN&amp;lt;T&amp;gt;(items: Array&amp;lt;T&amp;gt;, n: Number, comp: (T) -&amp;gt; Comparable): Array&amp;lt;T&amp;gt; =
    (items orderBy -comp($))[0 to (n - 1)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now calling it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;topN&amp;lt;Number&amp;gt;(payload.numbers, 3, (n) -&amp;gt; n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler checks that &lt;code&gt;payload.numbers&lt;/code&gt; is &lt;code&gt;Array&amp;lt;Number&amp;gt;&lt;/code&gt;. If it's &lt;code&gt;Array&amp;lt;String&amp;gt;&lt;/code&gt;, you get a compile error. Not a runtime surprise 4 months later.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The pipe Function: Type-Safe Composition
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;pipe&lt;/code&gt; function chains transformations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun pipe&amp;lt;T&amp;gt;(value: T, fns: Array&amp;lt;(T) -&amp;gt; T&amp;gt;): T =
    fns reduce (fn, acc = value) -&amp;gt; fn(acc)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipe&amp;lt;Array&amp;lt;Number&amp;gt;&amp;gt;(payload.numbers, [
  (arr) -&amp;gt; arr distinctBy $,
  (arr) -&amp;gt; arr orderBy $
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every function in the array must accept and return &lt;code&gt;Array&amp;lt;Number&amp;gt;&lt;/code&gt;. If one function returns a different shape, the compiler catches it.&lt;/p&gt;

&lt;p&gt;Before generics, a function that returned &lt;code&gt;Array&amp;lt;Object&amp;gt;&lt;/code&gt; instead of &lt;code&gt;Array&amp;lt;Number&amp;gt;&lt;/code&gt; would silently change the data shape downstream. With &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; enforced, every step in the chain must maintain the type contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Bugs Generics Caught
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bug 1: Wrong sort order for 4 months.&lt;/strong&gt; A flow passed &lt;code&gt;Array&amp;lt;Object&amp;gt;&lt;/code&gt; (customer records) to &lt;code&gt;topN&lt;/code&gt; expecting &lt;code&gt;Array&amp;lt;Number&amp;gt;&lt;/code&gt;. The comparator extracted a String field instead of Number. Sorting was alphabetical, not by magnitude. "Customer #9" ranked above "Customer #10" because "9" &amp;gt; "1" in string comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 2: Shape mismatch in pipe chain.&lt;/strong&gt; Step 1 in a pipe chain returned &lt;code&gt;Array&amp;lt;Object&amp;gt;&lt;/code&gt; but step 2 expected &lt;code&gt;Array&amp;lt;Number&amp;gt;&lt;/code&gt;. The output was nested objects where flat numbers were expected. Downstream calculations used the nested objects as numbers — JavaScript-style coercion produced non-obvious wrong values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 3: Comparator type confusion.&lt;/strong&gt; A topN call used &lt;code&gt;(r) -&amp;gt; r.score&lt;/code&gt; where &lt;code&gt;r.score&lt;/code&gt; was a String "95" not Number 95. The ordering was lexicographic: "87" &amp;gt; "100" because "8" &amp;gt; "1". Top 3 by score returned the wrong 3 records.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trap: Runtime Version Incompatibility
&lt;/h2&gt;

&lt;p&gt;This requires DataWeave 2.5 (Mule 4.5+). The &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; syntax doesn't parse on older runtimes.&lt;/p&gt;

&lt;p&gt;If your team runs mixed Mule versions — some apps on 4.5, some on 4.4 — a shared module with generics compiles on one server and crashes on another.&lt;/p&gt;

&lt;p&gt;I version-gate my modules now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;modules/utils-v25.dwl&lt;/code&gt; — generics, for Mule 4.5+ apps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modules/utils.dwl&lt;/code&gt; — no generics, backward compatible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 4.4 apps use the untyped version until they're upgraded. Not ideal, but better than breaking production.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Generics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use generics when&lt;/th&gt;
&lt;th&gt;Don't bother when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared utility functions used across 3+ apps&lt;/td&gt;
&lt;td&gt;One-off inline transforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions that accept multiple types (topN, pipe, map wrappers)&lt;/td&gt;
&lt;td&gt;Functions with a single known input type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your team is on Mule 4.5+&lt;/td&gt;
&lt;td&gt;Mixed runtime versions with no upgrade path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>typescript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Config-Driven DataWeave Mapping: The Null Trap Nobody Warns You About</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sun, 05 Apr 2026 03:12:32 +0000</pubDate>
      <link>https://dev.to/thasha/config-driven-dataweave-mapping-the-null-trap-nobody-warns-you-about-2k8k</link>
      <guid>https://dev.to/thasha/config-driven-dataweave-mapping-the-null-trap-nobody-warns-you-about-2k8k</guid>
      <description>&lt;p&gt;I had 12 tenants sending the same data in 12 different field names. Instead of writing 12 transforms, I built one config-driven mapper. It worked great — until tenant 8's schema drifted and 3,400 records silently filled with nulls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Config-driven mapping lets you add tenants without code changes — one JSON config per client&lt;/li&gt;
&lt;li&gt;The core is 5 lines of DataWeave using dynamic key expressions&lt;/li&gt;
&lt;li&gt;The trap: if a config field references a source column that doesn't exist, you get null — silently&lt;/li&gt;
&lt;li&gt;The fix: validate config against actual record keys before mapping&lt;/li&gt;
&lt;li&gt;Went from 2-day onboarding to 30 minutes per new client&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem: 12 Tenants, 12 Schemas
&lt;/h2&gt;

&lt;p&gt;All 12 clients sent the same logical data: customer ID, customer name, order amount. But the field names were different:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Customer ID&lt;/th&gt;
&lt;th&gt;Customer Name&lt;/th&gt;
&lt;th&gt;Order Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Acme Corp&lt;/td&gt;
&lt;td&gt;cust_id&lt;/td&gt;
&lt;td&gt;cust_name&lt;/td&gt;
&lt;td&gt;order_amt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Globex Inc&lt;/td&gt;
&lt;td&gt;customer_number&lt;/td&gt;
&lt;td&gt;customer_name&lt;/td&gt;
&lt;td&gt;amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initech&lt;/td&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;total&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Writing 12 separate DataWeave transforms meant every output schema change required updating 12 files. Deployment coordination across 12 configs was a disaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: 5 Lines of Config-Driven Mapping
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%dw 2.0
output application/json
var config = payload.mappingConfig
---
payload.sourceData map (record) -&amp;gt; ({
  (config map (field) -&amp;gt; ({
    (field.target): record[field.source]
  }))
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping config is a JSON array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cust_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"customerName"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order_amt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orderAmount"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New tenant? Create a config file with their field names. Zero code deployment.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trap: Silent Nulls From Missing Fields
&lt;/h2&gt;

&lt;p&gt;Tenant 8's source system renamed &lt;code&gt;cust_id&lt;/code&gt; to &lt;code&gt;customer_id&lt;/code&gt; in a schema update. Our config still said &lt;code&gt;cust_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;record["cust_id"]&lt;/code&gt; on a record that has &lt;code&gt;customer_id&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt;. Not an error. Not a warning. Just &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;3,400 records went through with &lt;code&gt;customerId: null&lt;/code&gt;. The downstream CRM accepted them — null is valid JSON. I caught it 4 days later when the CRM team asked why customer names were missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Config Validation Before Mapping
&lt;/h2&gt;

&lt;p&gt;Add 3 lines before the mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var sourceKeys = keysOf(payload.sourceData[0])
var configSources = config map (field) -&amp;gt; field.source
var missing = configSources -- sourceKeys
---
if (sizeOf(missing) &amp;gt; 0)
  error("Config references fields not in source: " ++ (missing joinBy ", "))
else
  payload.sourceData map (record) -&amp;gt; ({
    (config map (field) -&amp;gt; ({
      (field.target): record[field.source]
    }))
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the flow fails fast with: "Config references fields not in source: cust_id". Clear. Actionable. Catches the mismatch before it produces 10,000 records with null fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dynamic Key Expression Trap
&lt;/h2&gt;

&lt;p&gt;The parentheses in &lt;code&gt;(field.target): value&lt;/code&gt; are critical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// CORRECT — dynamic key from variable
(field.target): record[field.source]

// WRONG — literal string "field.target" as key
field.target: record[field.source]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without parentheses, DataWeave treats &lt;code&gt;field.target&lt;/code&gt; as a literal key name. Your output has &lt;code&gt;{"field.target": "C-100"}&lt;/code&gt; instead of &lt;code&gt;{"customerId": "C-100"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the single most common mistake when building dynamic mappings. The parentheses syntax &lt;code&gt;(expression): value&lt;/code&gt; evaluates the expression and uses the result as the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every config-driven transform gets a validation step that runs before the mapping&lt;/li&gt;
&lt;li&gt;Config files are versioned alongside source system API versions&lt;/li&gt;
&lt;li&gt;I log the config-to-schema diff on every run — catches drift before it causes nulls&lt;/li&gt;
&lt;li&gt;New tenant onboarding takes 30 minutes instead of 2 days&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>tutorial</category>
      <category>integration</category>
    </item>
    <item>
      <title>DataWeave Custom Functions: The Module Trap That Wastes Hours</title>
      <dc:creator>ThaSha</dc:creator>
      <pubDate>Sat, 04 Apr 2026 04:20:01 +0000</pubDate>
      <link>https://dev.to/thasha/dataweave-custom-functions-the-module-trap-that-wastes-hours-1j0i</link>
      <guid>https://dev.to/thasha/dataweave-custom-functions-the-module-trap-that-wastes-hours-1j0i</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your &lt;code&gt;fun&lt;/code&gt; declaration works inline but breaks with "Unable to resolve reference" when moved to a &lt;code&gt;.dwl&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;Module files cannot contain &lt;code&gt;output&lt;/code&gt; or &lt;code&gt;---&lt;/code&gt; — delete those lines and it works&lt;/li&gt;
&lt;li&gt;The error message lies — it says "resolve reference" not "invalid module structure"&lt;/li&gt;
&lt;li&gt;Recursive custom functions like hand-rolled &lt;code&gt;pow&lt;/code&gt; will stack overflow in production&lt;/li&gt;
&lt;li&gt;Higher-order functions taking lambdas need explicit type annotations or imports fail silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So on a Friday afternoon, I did the obvious refactor: extract the functions into a shared &lt;code&gt;.dwl&lt;/code&gt; module file. Import once, use everywhere.&lt;/p&gt;

&lt;p&gt;It broke immediately. 2 hours of debugging a misleading error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Working Inline Code
&lt;/h2&gt;

&lt;p&gt;Here's what I had in each flow, working perfectly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%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 &amp;lt;= 0) 1 else base * pow(base, exp - 1)
fun applyDiscount(price: Number, discountFn: (Number) -&amp;gt; Number): Number = discountFn(price)
var discount = (p: Number) -&amp;gt; if (p &amp;gt; 500) p * 0.9 else p
---
payload.products map (p) -&amp;gt; ({
  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 &amp;gt;= payload.freeShippingThreshold
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three custom functions: &lt;code&gt;roundTo&lt;/code&gt; for decimal precision, &lt;code&gt;pow&lt;/code&gt; as a recursive helper, and &lt;code&gt;applyDiscount&lt;/code&gt; — a higher-order function that takes a lambda for the discount strategy. Clean input/output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laptop Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1299.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Standing Desk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;799&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;29.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Office Chair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;449&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"taxRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0875&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"freeShippingThreshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output: each product with computed discount, tax, and shipping flag. No issues.&lt;/p&gt;




&lt;p&gt;100 production-ready DataWeave patterns with tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;mulesoft-cookbook on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Trap 1: The Module Structure Constraint
&lt;/h2&gt;

&lt;p&gt;I copied the entire script into &lt;code&gt;src/main/resources/modules/pricing.dwl&lt;/code&gt;. Imported it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * from modules::pricing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error: &lt;strong&gt;"Unable to resolve reference of pricing"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The function was right there. The path was correct. I checked classpath, file encoding, permissions. Nothing worked.&lt;/p&gt;

&lt;p&gt;The problem: my module file still had &lt;code&gt;output application/json&lt;/code&gt; and the &lt;code&gt;---&lt;/code&gt; separator from the inline script. Module files &lt;strong&gt;cannot&lt;/strong&gt; contain these. DataWeave modules only allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fun&lt;/code&gt; declarations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;var&lt;/code&gt; declarations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt; definitions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;namespace&lt;/code&gt; definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;output&lt;/code&gt;. No &lt;code&gt;---&lt;/code&gt;. No body expression.&lt;/p&gt;

&lt;p&gt;The correct module file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 &amp;lt;= 0) 1 else base * pow(base, exp - 1)
fun applyDiscount(price: Number, discountFn: (Number) -&amp;gt; Number): Number = discountFn(price)
var discount = (p: Number) -&amp;gt; if (p &amp;gt; 500) p * 0.9 else p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete &lt;code&gt;%dw 2.0&lt;/code&gt;, delete &lt;code&gt;output application/json&lt;/code&gt;, delete &lt;code&gt;---&lt;/code&gt;, delete the body. Keep only declarations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: Recursive Functions Without Depth Limits
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;pow&lt;/code&gt; function is recursive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun pow(base: Number, exp: Number): Number = if (exp &amp;lt;= 0) 1 else base * pow(base, exp - 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The fix: use &lt;code&gt;dw::util::Math&lt;/code&gt; functions instead of hand-rolling recursion for anything that touches production data. Built-in functions handle edge cases you haven't thought of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 3: Higher-Order Function Type Errors
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;applyDiscount&lt;/code&gt; takes a lambda parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun applyDiscount(price: Number, discountFn: (Number) -&amp;gt; Number): Number = discountFn(price)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Always type-annotate function parameters explicitly. &lt;code&gt;(Number) -&amp;gt; Number&lt;/code&gt; tells the caller exactly what shape of lambda to pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Module Files Can and Cannot Contain
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Allowed in modules&lt;/th&gt;
&lt;th&gt;NOT allowed in modules&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;fun&lt;/code&gt; declarations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;output&lt;/code&gt; directive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;var&lt;/code&gt; declarations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;---&lt;/code&gt; separator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;type&lt;/code&gt; definitions&lt;/td&gt;
&lt;td&gt;Body expressions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;namespace&lt;/code&gt; definitions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;%dw 2.0&lt;/code&gt; header&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This constraint exists because modules are imported, not executed. They provide definitions for other scripts to use. They don't produce output themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Do Now
&lt;/h2&gt;

&lt;p&gt;Before deploying any shared &lt;code&gt;.dwl&lt;/code&gt; module, I run a 30-second check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the file contain &lt;code&gt;output&lt;/code&gt;? Remove it.&lt;/li&gt;
&lt;li&gt;Does the file contain &lt;code&gt;---&lt;/code&gt;? Remove it.&lt;/li&gt;
&lt;li&gt;Does any function use recursion? Replace with built-in.&lt;/li&gt;
&lt;li&gt;Are all function parameters type-annotated? If not, add types.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Would have saved me those 2 Friday hours.&lt;/p&gt;




&lt;p&gt;100 patterns with MUnit tests: &lt;a href="https://github.com/shakarbisetty/mulesoft-cookbook" rel="noopener noreferrer"&gt;github.com/shakarbisetty/mulesoft-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;60-second video walkthroughs: &lt;a href="https://www.youtube.com/@SanThaParv?sub_confirmation=1" rel="noopener noreferrer"&gt;youtube.com/@SanThaParv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dataweave</category>
      <category>mulesoft</category>
      <category>debugging</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
