A reusable Mule 4 module that turns any error thrown in a flow into a consistent JSON error response and matching HTTP status code. Add it to an API's error handler and every failure comes back in one predictable shape, no matter what threw it.
Mule's default error handling returns inconsistent shapes to the caller. APIkit errors look one way, HTTP and connector errors another. Rebuilding a standard response by hand in every API is repetitive and easy to get wrong. This module does it once. You get a single response format and per-error customization in DataWeave, plus optional pass-through of downstream API errors, with no global configuration to wire up.
Every handled error becomes two things: an HTTP status code and a JSON body. The top-level key (error by default) is configurable.
Status code: 400
{
"error": {
"code": 400,
"reason": "Bad Request",
"message": "Error validating response"
}
}Status code and reason phrase follow the HTTP RFC 7231 codes for client 4xx and server 5xx errors. The message is free-form: a string, an array, or an object. It defaults to the Mule error.description.
- Mule runtime: 4.6+, Enterprise
- Java: 17
- MuleSoft Enterprise Maven credentials in your
settings.xml. The build pulls an EEprovideddependency, so every Maven build needs them. See configuring the EE repository.
- Quick Start
- Features
- Operations
- Configuration
- Error Handling Tips
- Versions
- Caveats
- Building and Deploying
-
Build the module and add it as a dependency in your app. Install it locally or deploy it to your Anypoint Exchange; either way you set the
groupIdat build time. See Building and Deploying for both paths, then use thatgroupIdand the version your build produced (1.0.0-SNAPSHOTby default).<dependency> <groupId>YOUR_GROUP_ID</groupId> <artifactId>api-error-handler</artifactId> <version>1.0.0-SNAPSHOT</version> <classifier>mule-plugin</classifier> </dependency>
-
Delete APIkit's generated
on-error-propagateandon-error-continueblocks. -
In your error handler, drop in Process Error. After it, set
vars.httpStatus = attributes.httpStatusand logattributes.errorLog. -
Pass the status through the listener:
statusCode="#[vars.httpStatus default 200]"onhttp:response, and#[vars.httpStatus default 500]with#[payload]as the body onhttp:error-response.
That is enough to return consistent errors. For custom responses, copy examples/customErrors.dwl into your app at src/main/resources/errors/customErrors.dwl, then reference it in Process Error (see Custom Errors Tab). Configuration covers every step in depth.
- One JSON shape and HTTP status for every error, whatever threw it.
- Per-error customization in DataWeave for HTTP and APIkit errors, and for your own error types.
- A configurable default for unmatched errors, which is 500 Internal Server Error.
- Optional pass-through of a downstream API's error, so a system API failure can reach the caller.
- Optional use of the generated
error.descriptionas the message. - A
messagefield that accepts a string, an array, or an object. - A separate
errorLogstring for logging. It merges the current message, the previous error, and the description, then removes duplicates and empties. - Works inside
on-error-continueandon-error-propagate. - No global configuration.
The error messages that are used by this module are described below. The module allows for customizing which one you use for your response to the caller via dataweave. The module aggregates them all together into a string for error logging.
- Error Message: the error message generated from the matching error in Common Errors or Custom Errors. This defaults to the Error Object Description. If no previous error is found, this is returned as the error message.
- Previous Error Message: the error message pulled from the error object's response payload (e.g. called API's error response). If found, this is returned as the error message. This can be completely customized but defaults to the error response payload produced by this module.
- Error Object Description: the error message in the error object's description. This is used for the Error Message in Common Errors or Custom Errors by default. However, those can be customized to use something else instead.
This operation processes any exception to a proper API error response. It provides the outputs below.
payload: the HTTP response body with the error details.attributes.httpStatus: the HTTP response status code.attributes.errorLog: the string of all aggregated errors: error message, previous error message, and error object's description. The module converts all types to strings and removes duplicates and empties.
Configure and use the error handler in an app. There is no separate global configuration. Build and install the module (see Building and Deploying) and add the Maven dependency shown above; it then appears in the Studio palette.
This replaces the generated error handling so a few steps have to be done to prepare the app to use the error handler.
- Delete the APIKit's auto-generated error blocks (
on-error-propagate/on-error-continue) before using this module. - Set the outbound HTTP Status variable,
vars.httpStatuswhen using APIKit, from the HTTP status attribute set by the module:attributes.httpStatus. This is how the status code is sent to the caller. - Update HTTP Listener's response values to properly use the generated body and HTTP status from the module. See below.
Add vars.httpStatus to the listener's http:response and http:error-response elements. Also make sure that the http:error-response element has the payload as its body. These are child elements of <http:listener>.
<http:response
statusCode="#[vars.httpStatus default 200]">
</http:response>
<http:error-response
statusCode="#[vars.httpStatus default 500]">
<http:body ><![CDATA[#[payload]]]></http:body>
</http:error-response>
- Drag the Process Error operation from Studio's palette into the error handler to transform errors into API response. Place the module inside an error block:
on-error-continue. - Set vars.httpStatus = attributes.httpStatus
- Log error message: attributes.errorLog
- Reference this error handler in the APIkit's main flow to be the top-level error handler for the API.
Module XML
<module-error-handler-plugin:process-error
doc:name="Process Error"
doc:id="ca10a245-319b-40c4-b6af-7de5c2ca83de" />
Module XML with Custom Errors
<module-error-handler-plugin:process-error
doc:name="Process Error"
doc:id="ca10a245-319b-40c4-b6af-7de5c2ca83de">
<module-error-handler-plugin:custom-errors>
<![CDATA[#[${file::errors/customErrors.dwl}]]]>
</module-error-handler-plugin:custom-errors>
</module-error-handler-plugin:process-error>
An example of the full error handler flow is shown below. This example uses the built-in logger for logging the error.
<error-handler name="api-error-handler">
<on-error-continue
enableNotifications="true"
logException="true"
doc:name="On Error Continue">
<module-error-handler-plugin:process-error
doc:name="Process Error"
doc:id="ca10a245-319b-40c4-b6af-7de5c2ca83de" />
<set-variable
variableName="httpStatus"
value="#[attributes.httpStatus]"
doc:name="Set HTTP Status Code" />
<logger
level="ERROR"
doc:name="Log Error"
doc:id="93d3fc4f-cc4f-453a-be4c-2c80434bda2b"
message='#[output application/json --- { code: attributes.httpStatus, message: attributes.errorLog }]' />
</on-error-continue>
</error-handler>
Customize APIKit & HTTP Error Messages
Modify the error message for the APIKit and HTTP errors on the Common Errors tab. This field supports dataweave for dynamically generated messages if needed. The response status code and error reason (phrase) cannot be changed for common errors on this tab.
- Additional errors not covered here can be mapped to the same status codes with the Custom Errors feature.
- If you want to change the status code or reason, use the Custom Errors feature to override the desired APIKit or HTTP exceptions.
You can set the error message to the generated error description from the error object, error.description, based on the Use Generated Error Description Instead selection. If it evaluates to true, the generated error will be used as the error message. If it evaluates to false, the user-provided message will be used. This selection only applies to common errors. It does not apply to custom errors. If you want to add dynamic error messages via dataweave, then set this to false and add the dataweave into the message fields.
Note: The only exception to using generated errors is the dataweave Expression Error, which does not use the generated error description, regardless of the setting since this can be a security risk. If you want to add the generated error to this error, you will have to explicitly do that in its message field.
Customize Full Error Definitions
You can add any number of custom error definitions for the module to include in the mapping. This is done by defining these custom error mappings inline or in a dataweave file. The screenshot shows using a file.
A file is recommended. This file should be in or below src/main/resources folder in the Mule app. Recommended practice is to put it in an errors folder: src/main/resources/errors.
When adding the file name to the Custom Errors field in the module, make sure to include the full relative path from the resources folder.
Example: if the custom errors file is src/main/resources/errors/customErrors.dwl then this field should be errors/customErrors.dwl. The full syntax for importing a dataweave file and processing it is listed below.
${file::errors/customErrors.dwl}
The custom errors must be an object of objects with the fields below.
- Key: Mule error type used to match. Example:
HTTP:BAD_REQUEST - Value: (object)
code: HTTP status code to send in response. This is a number.reason: Error reason phrase to send in JSON body response. This is a string.message: Error details to send in JSON body response. A string is preferred for this field, but any type is allowed.
Dataweave script is allowed in each field value. To access the error object in this definition, you use error as normal.
Custom errors override common errors. If you want to override a common error's status or reason, and not just the message, you would add an entry for that error in the custom errors definition, which will completely override the common error.
The template file examples/customErrors.dwl is ready to copy and shows the common patterns:
APP:UNAUTHORIZEDandAPP:SERVICE_UNAVAILABLEmap app-raised errors to specific responses.HTTP:INTERNAL_SERVER_ERRORoverrides the common 500 to pass a conforming downstream message through withgetPreviousErrorMessage.MULE:UNKNOWNhandles non-standard status codes such as495and passes the whole downstream body through withgetPreviousError.
The module provides these functions, defined in common.dwl and imported with import * from module_error_handler_plugin::common.
getErrorTypeAsString: Gets the string for the current Mule error type. This corresponds to the keys in the custom error object. Example:HTTP:INTERNAL_SERVER_ERROR.getPreviousErrorMessage: Gets a downstream API's error message (theerror.messagefield) from the error object, for APIs that conform to this module's response format. Returns null if it is not available, so it pairs with thedefaultoperator. It reads only the publicerror.errorMessageAPI, so it is safe on Java 17 (Mule 4.6+).getPreviousError: Gets the entire downstream error body as a string, for propagating errors whose body does not conform to this module's format (e.g. SOAP faults, HTML, third-party JSON). Returns null if it is not available, so it pairs with thedefaultoperator. It reads only the publicerror.errorMessageAPI, so it is safe on Java 17 (Mule 4.6+).toString: Converts any type to a string. If not a string, it uses write() with Java format. If empty, then returns empty string or the value specified in the second parameter.
General Configuration
General configuration is defined on the Advanced tab. This includes the Error Object definition and Use Previous Error feature.
The error object definition takes the standard Mule Error by default, which is the recommended setting. You can change this as long as the provided object has the same fields as the Mule Error.
Connectors usually generate their own error responses and wrap the actual external-system response inside the error object. That loses the external response, so it never reaches the API's caller. The previous error feature retrieves the external system's response from the error object and uses it as the error message.
A common scenario is when a system API generates an error that needs to get propagated back to the caller of the experience or process API. Using normal error handling, like error.description, the SOAP fault or 500 response from the called system is not logged or propagated. These items are nested in the error object here: error.errorMessage.payload and error.errorMessage.attributes. Always access the failed message through these public error.errorMessage selectors; avoid internal fields such as typedValue, which raise an illegal-access error on Java 17 (Mule 4.6+). Be aware that payload and attributes are not selectable if the content is Binary. If the type is Binary, then you must read the error payload, error.errorMessage.payload, as the correct MIME type if you want to access a specific field using a selector.
This feature will automatically replace the message field for all errors with the previous error defined by the provided dataweave if one exists. If the previous error does not exist or is empty, then it will leave the message field with its current value. This feature does not append the previous error to the current one. It simply replaces and is best used to propagate downstream errors up the API stack.
The dataweave should resolve to a string but any type is allowed. You can override in any manner; the template custom error file gives a full example of converting nested errors to strings.
Set this field to an empty string if you do not want to propagate previous errors. This is the default value
This field allows you to customize the JSON key name where the error payload is set. This defaults to error, which is shown in the examples. This only supports changing the name of the top-level key; it does not change any other format. If you want the error payload to be the top-level element in the response, then set this field to empty string.
Default Response: error
{
"error": {
"code": 400,
"reason": "Bad Request",
"message": "Error validating response"
}
}
Custom Response: errorDetails
{
"errorDetails": {
"code": 400,
"reason": "Bad Request",
"message": "Error validating response"
}
}
Custom Response: empty string
{
"code": 400,
"reason": "Bad Request",
"message": "Error validating response"
}
If you want to override the default error when no errors matched (500 - Internal Server Error), use the error type UNKNOWN in the custom errors definition. This allows updating unmapped errors, which is the default error. If you only want to change the message, update it in the Common Errors tab, instead of in Custom Errors.
"UNKNOWN": {
"code":528,
"reason": "API Error",
"message": error.description
}
If you want to override the error when no errors matched (500 - Internal Server Error) for non-standard HTTP status code responses, like 455, use the error type MULE:UNKNOWN in the custom errors definition. This allows updating unknown errors, which correspond to non-standard HTTP status codes when coming from HTTP requester. If you don't want to distinguish between unmapped errors (UNKNOWN) and unknown errors (MULE:UNKNOWN), then use the UNKNOWN type.
This propagates the status code, reason phrase, and message from the external system's error response. The MULE:UNKNOWN entry in examples/customErrors.dwl shows the pattern with getPreviousError.
As described in the Use Previous Error feature in the Advanced tab, sometimes it is useful to get the downstream error when it is generated from a connector. That feature forces the downstream error propagation for all errors.
Sometimes, you only want to propagate certain errors and not all. It is best practice to propagate 500 error message from Mule APIs up the API network. However, you may not want to propagate the error if it was a connection or authentication issue, which may provide info that you don't want going back to the caller.
If you want to only propagate specific errors, then leave the Use Previous Error field empty and only put the previous error messages in specific common or custom errors.
To propagate a downstream error message, import the common functions and call getPreviousErrorMessage(error) default error.description in the relevant custom error. The HTTP:INTERNAL_SERVER_ERROR entry in examples/customErrors.dwl is a working example. In a common error field, which cannot import functions, select the nested value directly with error.errorMessage.payload.error.message default error.description.
The message field in the response body can be of any type. A string is recommended, but this can also be an array of errors if needed to show history of errors across APIs. To create an array of errors, follow the steps below.
- Create the list of errors with dataweave in the
messagefield contained in the Custom Error definition. This can be done in the message fields in the Common Error tab for the common errors. - Log the error in your flow while handling the array so it logs appropriately.
You can also simply set the message to a single string containing the merged array of errors.
If you have a special, non-standard, use-case where you need to force all of the API's errors to 500 with the same error message then you can use a single custom error to override all errors.
Do this by dynamically setting the error type key with the getErrorTypeAsString function. It always matches the current error. Avoid it unless you have a requirement.
import * from module_error_handler_plugin::common
var errorType = getErrorTypeAsString(error.errorType)
---
{
(errorType): {
code: 500,
reason: "Internal Service Error",
message: error.description
}
}
This repo tracks the latest code on the master branch. There are no published releases; fork or clone it and build from source. minMuleVersion in mule-artifact.json is 4.6.0, a floor: one build runs on 4.6 through the latest 4.x, so declaring 4.6 does not restrict use in newer apps.
Migrating from an older published version: the 6.0.0 line changed the response format and the XML namespace, so it is not a drop-in replacement. Remove the old module from the app first, and expect to adjust code that read the previous response shape.
- Custom errors fully replace the matching common error (code, reason, and message), not just the message.
- Previous-error DataWeave must use the public
error.errorMessageselectors. Internal fields such astypedValuethrow an illegal-access error on Java 17. - A
Binarydownstream body is not selectable. When a connector fails, its response sits onerror.errorMessage.payload, and the previous-error helpers read fields from it by media type, usually JSON. If that body isBinarywith no parseable media type, selectors returnnull. Read it first, for exampleread(error.errorMessage.payload, "application/json"), then select. - The Expression error ignores Use Generated Error Description. That toggle swaps a common error's message for the runtime's
error.description. Expression errors keep their static message on purpose, because a DataWeave failure'serror.descriptioncan expose the failing script and payload values to the caller. To include it anyway, puterror.descriptionin the Expression error's message field yourself. getPreviousErrorandgetPreviousErrorMessagereturnnullwhen nothing is found, so pair them withdefault.- DataSense shows the default
errorresponse key. A custom Response Key, or an empty one, changes the runtime shape but not the design-time type.
This is a Mule XML SDK module. It builds with plain Maven and needs no special command-line flags. It does pull an EE provided dependency, so your settings.xml needs MuleSoft Enterprise Maven credentials to build or deploy. An MUnit suite validates the module's functionality.
pom.xml ships a placeholder groupId, ORG_ID_TOKEN, because every consumer publishes under their own coordinates. You set your real groupId at build time. The two paths below show how.
Fork or clone the repo, then install to your local Maven repository (.m2) and depend on it from your app.
mvn -B clean install # install under the pom groupId
mvn -B clean install -Drevision=1.2.3 # stamp a version instead of the SNAPSHOT default
A plain mvn install installs under the literal ORG_ID_TOKEN placeholder. To install under your own groupId, do one of:
- Edit
<groupId>inpom.xml, replacingORG_ID_TOKEN, then runmvn -B clean install. - Or run
./build.sh install your.group.id, which substitutes the placeholder for that build only.
The version is CI-friendly: pom.xml sets <version>${revision}</version>, which defaults to a local SNAPSHOT and takes -Drevision to stamp a release. Add the matching Maven dependency in your app with that same groupId and version.
Publish to your organization's Exchange so teammates can add the module from the Studio palette.
build.sh substitutes ORG_ID_TOKEN with your Anypoint organization (business group) id and runs the deploy, so the published coordinates are right without editing the pom. The deploy authenticates against Exchange, so settings.xml needs a <server> whose <id> matches the repo id, Exchange2 by default.
REVISION=1.2.3 ./build.sh deploy 43ae201-c97b-4665-9310-e3ac89ce1c28
On a Windows agent, run build.sh through Git Bash or WSL.
build.sh takes the arguments below. Each also reads from an environment variable, and the argument wins when both are set.
- Build option:
package,install, ordeploy. - Group id: the Anypoint business group (organization) id to deploy to. Env
GROUP_ID. - Repo id: the Maven repository id, matching the
<server>id in yoursettings.xml. Defaults toExchange2. EnvREPO_ID. - Repo URL: the Maven repository URL. Defaults to the Anypoint Exchange URL. Env
REPO_URL. - Revision: the version to stamp, mapped to
-Drevision. EnvREVISION.
Pass extra Maven flags with MAVEN_ARGS, for example MAVEN_ARGS="-DskipTests".
Syntax
./build.sh [build option] [group id] [repo id] [repo url] [revision]
mule.version and mule.extensions.maven.plugin.version in pom.xml move together. The extension-model generator is tied to the runtime libraries it reads, so the 4.6 line pairs with generator 1.6.x. To target a higher floor, raise both. A newer generator cannot load a 4.6.0 model.




