Skip to content
 
 

Repository files navigation

API Error Handler

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.

Why This Module Exists

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.

Requirements

  • Mule runtime: 4.6+, Enterprise
  • Java: 17
  • MuleSoft Enterprise Maven credentials in your settings.xml. The build pulls an EE provided dependency, so every Maven build needs them. See configuring the EE repository.

Contents

Quick Start

  1. 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 groupId at build time. See Building and Deploying for both paths, then use that groupId and the version your build produced (1.0.0-SNAPSHOT by default).

    <dependency>
        <groupId>YOUR_GROUP_ID</groupId>
        <artifactId>api-error-handler</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <classifier>mule-plugin</classifier>
    </dependency>
  2. Delete APIkit's generated on-error-propagate and on-error-continue blocks.

  3. In your error handler, drop in Process Error. After it, set vars.httpStatus = attributes.httpStatus and log attributes.errorLog.

  4. Pass the status through the listener: statusCode="#[vars.httpStatus default 200]" on http:response, and #[vars.httpStatus default 500] with #[payload] as the body on http: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.

Features

  • 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.description as the message.
  • A message field that accepts a string, an array, or an object.
  • A separate errorLog string for logging. It merges the current message, the previous error, and the description, then removes duplicates and empties.
  • Works inside on-error-continue and on-error-propagate.
  • No global configuration.

Error Messages Used by Module

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.

Operations

Process Error

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.

Configuration

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.

Prepare the App

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.httpStatus when 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.

Update HTTP Listener Response

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>

Error Handler Flow

  1. 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.
  2. Set vars.httpStatus = attributes.httpStatus
  3. Log error message: attributes.errorLog
  4. 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>

Example

An example of the full error handler flow is shown below. This example uses the built-in logger for logging the error.

Error Handler Flow

<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>

Common Errors Tab

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.

Common Errors Tab

Use Generated Error Message

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.

Use Generated Error

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.

Custom Errors Tab

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.

Custom Errors Tab

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}

Error Format

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:UNAUTHORIZED and APP:SERVICE_UNAVAILABLE map app-raised errors to specific responses.
  • HTTP:INTERNAL_SERVER_ERROR overrides the common 500 to pass a conforming downstream message through with getPreviousErrorMessage.
  • MULE:UNKNOWN handles non-standard status codes such as 495 and passes the whole downstream body through with getPreviousError.

Common Functions

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 (the error.message field) 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 the default operator. It reads only the public error.errorMessage API, 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 the default operator. It reads only the public error.errorMessage API, 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.

Advanced Tab

General Configuration

General configuration is defined on the Advanced tab. This includes the Error Object definition and Use Previous Error feature.

Advanced Tab

Error Object

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.

Use Previous 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

Response Key Name for Payload

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"
}

Error Handling Tips

Override the Default Error

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
    }

Override Non-Standard HTTP Status Code Errors

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.

Downstream API Errors

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.

List of Errors

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 message field 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.

Force All Errors to 500

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
    }
}

Versions

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.

Caveats

  • Custom errors fully replace the matching common error (code, reason, and message), not just the message.
  • Previous-error DataWeave must use the public error.errorMessage selectors. Internal fields such as typedValue throw an illegal-access error on Java 17.
  • A Binary downstream body is not selectable. When a connector fails, its response sits on error.errorMessage.payload, and the previous-error helpers read fields from it by media type, usually JSON. If that body is Binary with no parseable media type, selectors return null. Read it first, for example read(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's error.description can expose the failing script and payload values to the caller. To include it anyway, put error.description in the Expression error's message field yourself.
  • getPreviousError and getPreviousErrorMessage return null when nothing is found, so pair them with default.
  • DataSense shows the default error response key. A custom Response Key, or an empty one, changes the runtime shape but not the design-time type.

Building and Deploying

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.

Build and Install Locally

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> in pom.xml, replacing ORG_ID_TOKEN, then run mvn -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.

Deploy to Anypoint Exchange

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.

  1. Build option: package, install, or deploy.
  2. Group id: the Anypoint business group (organization) id to deploy to. Env GROUP_ID.
  3. Repo id: the Maven repository id, matching the <server> id in your settings.xml. Defaults to Exchange2. Env REPO_ID.
  4. Repo URL: the Maven repository URL. Defaults to the Anypoint Exchange URL. Env REPO_URL.
  5. Revision: the version to stamp, mapped to -Drevision. Env REVISION.

Pass extra Maven flags with MAVEN_ARGS, for example MAVEN_ARGS="-DskipTests".

Syntax

./build.sh [build option] [group id] [repo id] [repo url] [revision]

Building for a Different Floor

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages