Error handling in Mule 4 is critical to building reliable and robust applications. In Mule 4, errors are handled using the Try scope. The Try scope allows you to catch and take errors in your application. Where in certain cases, errors will be handled at the global level using on error continue/on error propagate.
When an error occurs in Mule 4, it’s essential to understand the type of error and the context in which it occurred. Mule 4 provides several types of errors, including system and application errors. System errors occur at the infrastructure level and can be caused by network connectivity or server failures. On the other hand, application errors occur within your application and can be caused by issues such as bad data inputs or invalid configurations.
This blog guides you on using Mule 4’s Global Error Handling feature to its full potential, including why this feature is useful and helps to unify the API ecosystem as a whole. It also explains how this feature allows Anypoint Platform to handle application-level errors using the Global Error Handler.
A Global Error Handler can also be defined for the whole application. It is revoked only when there is no scope level or processor level error handler. We can do so by adding the ‘Error Handler’ scope to a Mule configuration file. Typically, it is put in the global configuration file. Then this handler needs to be referenced as the application’s default error handler.
Key features and use cases
Execution failures are represented by Mule errors that have the following components:
- A description of the problem.
- A type that is used to characterise the problem.
- A cause, the underlying Java Throwable that resulted in the failure.
- An optional error message, which is used to include a proper Mule Message regarding the problem.
For example, when an HTTP request fails with a 404 status code, a Mule error provides the following information:
Description: HTTP GET on resource ‘http://localhost:8081/example’ failed: not found (404)
Type: HTTP:NOT_FOUND
An API (like Experience API, Process API, or System API) will be throwing a particular error based on a specific error or business condition.
Below are a few examples:
- Bad Request or Validation Failed (400) — When any specific field validation or business validation failed in a specific API.
- Internal Service Error (500) — When something wrong happens in a specific API.
- Downstream API will be throwing an error which needs to be propagated to upstream API/Caller API/Consumer API.
A few business use cases regarding error handling
Case #1: To raise a Bad Request (APP:BAD_REQUEST) or Validation (APP:VALIDATION) error in the application.
- In this case, the API owner should set Error Type (APP:BAD_REQUEST) and Provide Error Description (The order number is missing the request).
Case #2: Calling HTTP API and it returns specific error code: Ex: HTTP Status as 404 (NOT_FOUND).
- The application owner can handle it as per specific needs if the owner wants to set a custom error message/description
- If an exception is not being handled then it will be handled in the global error handler.
- If the application owner wants to change the HTTP Status Code, then it should be handled at the application level and raise a custom error after that.
Case #3: To raise an error with a custom error code and error message description.
- Before raising a custom error (APP:CUSTOM), the application owner can set a few variables per the requirement.
errorDesc variable with appropriate error description as per requirement.
errorCode variable with appropriate error code (HTTP Error Code) as per requirement.
What can be handled at the interface level/API or can be handled at the common level?
Error handling at interface level/API level
Business-related validation and error scenarios will be handled at the API level. Example: Business Validation Failure.
- Set HTTP STATUS = 400, Set Error Payload and raise APP:VALIDATION.
- Custom Error Mapping would be required at specific processor Ex: VALIDATION:INVALID_BOOLEAN → APP:VALIDATION / MULEAPI:CUSTOM.
- Set HTTP STATUS = 500, Set Error Payload and raise APP:ORDER_LOST , APP:ORDER_RELATED_CUST_ERROR, APP:CUSTOM as per requirement.
All Common scenarios. Ex: Unauthorised, Forbidden etc., will be handled at the common level
Variable Name errorDesc, errorCode, which will be created in the specific API and needs to be set/populated before raising any custom error as per API needs for custom error description:
Note: The scenarios below are not being covered as of now, but if those are needed in the future, then they should be considered/implemented as an enhancement.
- Batch
- Queue
- Until successful/retry exhausted
- Async
Below are HTTP Error codes which are being handled by a global common error handler.
Asset details -global-error-handler → pom.xml
<?xml version=”1.0″ encoding=”UTF-8″?>
<project
xmlns=”http://maven.apache.org/POM/4.0.0″ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>27bb07d4–27ae-4c16–8b12–083d4de85cb7</groupId>
<artifactId>global-error-handler</artifactId>
<version>1.0.0</version>
<packaging>mule-application</packaging>
<name>global-error-handler</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<app.runtime>4.4.0</app.runtime><mule.maven.clean.plugin.version>3.2.0</mule.maven.clean.plugin.version><mule.maven.plugin.version>3.8.2</mule.maven.plugin.version>
<munit.version>2.3.14</munit.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${mule.maven.clean.plugin.version}</version>
</plugin>
<plugin>
<groupId>org.mule.tools.maven</groupId>
<artifactId>mule-maven-plugin</artifactId>
<version>${mule.maven.plugin.version}</version>
<extensions>true</extensions>
<configuration>
<classifier>mule-plugin</classifier>
</configuration>
</plugin>
<plugin>
<groupId>com.mulesoft.munit.tools</groupId>
<artifactId>munit-maven-plugin</artifactId>
<version>${munit.version}</version>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
<goal>coverage-report</goal>
</goals>
</execution>
</executions>
<configuration>
<coverage>
<runCoverage>true</runCoverage>
<formats>
<format>html</format>
</formats>
</coverage>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
groupId>org.mule.connectors</groupId>
<artifactId>mule-http-connector</artifactId>
<version>1.7.3</version>
<classifier>mule-plugin</classifier>
</dependency>
<dependency>
<groupId>org.mule.connectors</groupId>
<artifactId>mule-sockets-connector</artifactId>
<version>1.2.2</version>
<classifier>mule-plugin</classifier>
</dependency>
<! — MUnit : START →
<dependency>
<groupId>org.mule.weave</groupId>
<artifactId>assertions</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mulesoft.munit</groupId>
<artifactId>munit-runner</artifactId>
<version>${munit.version}</version>
<classifier>mule-plugin</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mulesoft.munit</groupId>
<artifactId>munit-tools</artifactId>
<version>${munit.version}</version>
<classifier>mule-plugin</classifier>
<scope>test</scope>
</dependency>
<! — MUnit : END →
</dependencies>
<repositories>
<repository>
<id>anypoint-exchange-v3</id>
<name>Anypoint Exchange</name>
<url>https://maven.anypoint.mulesoft.com/api/v3/maven</url>
<layout>default</layout>
</repository>
<repository>
<id>mulesoft-releases</id>
<name>MuleSoft Releases Repository</name>
<url>https://repository.mulesoft.org/releases/</url>
<layout>default</layout>
</repository>
<! — Shared Mule Services Repository →
<repository>
<id>Exchange3</id>
<name>Anypoint Exchange v3 Repository</name>
<url>https://maven.anypoint.mulesoft.com/api/v3/organizations/${project.groupId}/maven</url>
<layout>default</layout>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>mulesoft-releases</id>
<name>MuleSoft Releases Repository</name>
<layout>default</layout>
<url>https://repository.mulesoft.org/releases/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<distributionManagement>
<! — Target Anypoint Organization Repository →
<repository>
<id>Exchange3</id>
<name>Anypoint Exchange v3 Repository</name>
<url>https://maven.anypoint.mulesoft.com/api/v3/organizations/${project.groupId}/maven</url>
<layout>default</layout>
</repository>
</distributionManagement>
</project>
Asset publish activity
mvn clean deploy
Asset reference
Follow the below steps to add Error Handler into Anypoint Studio:
- Add Error Handler Module. From Anypoint Studio -> Login to Anypoint Platform -> Search In Anypoint Exchange. Module Name : global-error-handler (Version : 1.0.0),
- Search Error Handler from Anypoint Exchange
- It will be added as a dependency in project pom.xml.
<dependency>
<groupId>27bb07d4–27ae-4c16–8b12–083d4de85cb7</groupId>
<artifactId>global-error-handler</artifactId>
<version>1.0.0</version>
<classifier>mule-plugin</classifier>
</dependency>
Asset configuration
Follow the below steps to configure Error Handler into the Mule Application:
(*) Search Error Handler Module from Anypoint Exchange and add this Error Handler module in the mule application
(*) The module will be added as maven dependency as described below
<dependency>
<groupId>27bb07d4–27ae-4c16–8b12–083d4de85cb7</groupId>
<artifactId>global-error-handler</artifactId>
<version>1.0.0</version>
<classifier>mule-plugin</classifier>
</dependency>
(*) Below are configurations which need to be added in the Mule application
(1) Import Config Properties
xml source view:
<import doc:name=”Import” doc:id=”811ee694-d3bc-4015–92f8–12ab440bfdd5″ file=”global-error-handler.xml” />
(2) Configure Error Handler at API Level
xml source view:
<flow name=”example-exp-api-main”>
<http:listener config-ref=”httpListenerConfig” path=”${http.api.path}”>
<http:response statusCode=”#[vars.httpStatus default 200]”>
<http:headers><![CDATA[#[vars.outboundHeaders default {}]]]></http:headers>
</http:response>
<http:error-response statusCode=”#[vars.httpStatus default 500]”>
<http:body><![CDATA[#[payload]]]></http:body>
<http:headers><![CDATA[#[vars.outboundHeaders default {}]]]></http:headers>
</http:error-response>
</http:listener>
<apikit:router config-ref=”pocalert-exp-api-config” />
<error-handler ref=”global-error-handler” />
</flow>
(3) Add Flow into API under Config File ( global-error.xml ) [ Note: Flow name must be api-error-handler-flow ]
Example of (Experience API, Process API and System API)
Set Payload Value:
output application/json
%dw 2.0
— –
vars.errorPayload default {}
(4) Raising Error (As-Is Error Propagate, Custom Error Propagate)
Below is the sample flow created, which takes queryParam (‘code’) and validates its value and raises a custom error.
ex: code=400 then HTTP 400, Bad Request will be raised
<flow name=”impl-error-scenarios-flow” doc:id=”61ecb468–4449–4bd5–857f-f79a6e3c37e0″ >
<set-variable value=’#[%dw 2.0 output application/json — - attributes.queryParams.”code” default null]’ doc:name=”errorCode” doc:id=”05b21b89-c046–4580-a00a-2a9e340798cc” variableName=”errorCode”/>
<choice doc:name=”Choice” doc:id=”5b683a5f-b8b4–4bfe-96f0–36665c0e13ba” >
<when expression=”#[vars.errorCode ~= 400]”>
<raise-error doc:name=”APP:BAD_REQUEST” doc:id=”becef598–49e9–4f10-acf4-ab130d825472″ type=”APP:BAD_REQUEST” description=”APP-BAD_REQUEST is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 401]”>
<raise-error doc:name=”APP:UNAUTHORIZED” doc:id=”46e08f4a-9758–47b7–9d0f-393953b96eb0″ type=”APP:UNAUTHORIZED” description=”APP-UNAUTHORIZED is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 403]”>
<raise-error doc:name=”APP:FORBIDDEN” doc:id=”4b42e743–86ec-4700-a76f-5f4d7252b7af” type=”APP:FORBIDDEN” description=”APP-FORBIDDEN is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 404]”>
<raise-error doc:name=”APP:NOT_FOUND” doc:id=”2e433519–9a09–4fda-be98–588d3cc05a0d” type=”APP:NOT_FOUND” description=”APP-NOT_FOUND is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 405]”>
<raise-error doc:name=”APP:METHOD_NOT_ALLOWED” doc:id=”2497946f-f8b8–497d-8ff9-f5a0345c27db” type=”APP:METHOD_NOT_ALLOWED” description=”APP-METHOD_NOT_ALLOWED is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 406]”>
<raise-error doc:name=”APP:NOT_ACCEPTABLE” doc:id=”a39046e1–350e-4eba-9dc5–847503012205″ type=”APP:NOT_ACCEPTABLE” description=”APP-NOT_ACCEPTABLE is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 408]”>
<raise-error doc:name=”APP:TIMEOUT” doc:id=”08011c3e-087a-4b38–9635–35979711dc6d” type=”APP:TIMEOUT” description=”APP-TIMEOUT is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 415]”>
<raise-error doc:name=”APP:UNSUPPORTED_MEDIA_TYPE” doc:id=”4ff413c3–3c87–4943-a39c-2e9f5eb62a6c” type=”APP:UNSUPPORTED_MEDIA_TYPE” description=”APP-UNSUPPORTED_MEDIA_TYPE is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 500]”>
<raise-error doc:name=”APP:INTERNAL_SERVER_ERROR” doc:id=”ddbe1bad-b8a5–4dbf-99e0-d43f88575500″ type=”APP:INTERNAL_SERVER_ERROR” description=”APP-INTERNAL_SERVER_ERROR is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 501]”>
<raise-error doc:name=”APP:NOT_IMPLEMENTED” doc:id=”3d368059–2256–4fb3-a235-ae5226c7458d” type=”APP:NOT_IMPLEMENTED” description=”APP-NOT_IMPLEMENTED is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 503]”>
<raise-error doc:name=”APP:SERVICE_UNAVAILABLE” doc:id=”5ddd35e4–3b2e-4cc4-aaa3–3a85acd2ada8″ type=”APP:SERVICE_UNAVAILABLE” description=”APP-SERVICE_UNAVAILABLE is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 600]”>
<raise-error doc:name=”APP:ANY” doc:id=”e7ff8f0e-7f16–4a2b-82fa-dde4d39e3554″ type=”APP:ANY” description=”APP-ANY is being sent”/>
</when>
<when expression=”#[vars.errorCode ~= 700]”>
<http:request method=”GET” doc:name=”Request — Invalid Call” doc:id=”87a4cf44-e18d-4709–80b5–5703bd5d6b4e” url=”https://www.google.com/example-abc” />
</when>
<when expression=”#[vars.errorCode ~= 800]”>
<ee:transform doc:name=”errorMessage” doc:id=”a2aff9a7–6952–412a-b6f3–1188669a2565″>
<ee:message>
</ee:message>
<ee:variables >
<ee:set-variable variableName=”errorMessage” ><![CDATA[%dw 2.0
output application/json
— –
“This is just testing with custom code”]]></ee:set-variable>
<ee:set-variable variableName=”errorDesc” ><![CDATA[%dw 2.0
output application/json
— –
“This is just testing with custom description”]]></ee:set-variable>
</ee:variables>
</ee:transform>
<raise-error doc:name=”APP:CUSTOM” doc:id=”33ac218f-f13d-4648-aef0–21526445f7d7″ type=”APP:CUSTOM”/>
</when>
<when expression=”#[vars.errorCode ~= 900]”>
<ee:transform doc:name=”errorCode and errorMessage” doc:id=”4630ff6d-ed2e-4bbf-a787-fa52e6a00b65″>
<ee:message>
</ee:message>
<ee:variables >
<ee:set-variable variableName=”errorMessage” ><![CDATA[%dw 2.0
output application/json
— –
“This is just testing with 900 code”]]></ee:set-variable>
<ee:set-variable variableName=”errorCode” ><![CDATA[“900”]]></ee:set-variable>
<ee:set-variable variableName=”errorDesc” ><![CDATA[%dw 2.0
output application/json
— –
“This is just testing with custom description”]]></ee:set-variable>
</ee:variables>
</ee:transform>
<raise-error doc:name=”APP:ANY (with erroCode and errorMessage)” doc:id=”bb418931–2d11–43f7-b1a3–9c8964084524″ type=”APP:ANY”/>
</when>
<when expression=”#[vars.errorCode ~= 1000]”>
<ee:transform doc:name=”dw Runtime fail” doc:id=”d7841ff7–0d44–47e7-a4f0–56f318896143″ ><ee:message >
<ee:set-payload ><![CDATA[%dw 2.0
output application/json
import * from dw::Runtime
— –
if (vars.errorCode ~= 1000)
fail(‘Data was empty with 1000 code’)
else null]]></ee:set-payload>
</ee:message>
</ee:transform>
</when>
<otherwise >
<json-logger:logger doc:name=”Logger — Implementation Error Scenarios” doc:id=”e34d7019-e46e-462b-9d0b-da87419698e8″ config-ref=”JSON_Logger_Config” tracePoint=”FLOW” category=”${app.name}” message=”Logger — Implementation Error Scenarios”/>
</otherwise>
</choice>
</flow>
Details about global-error-handler:
- global-error-handler contains all error continue blocks for each HTTP status code.
- Each block contains set variables like httpStatus (HTTP Status Code), errorHttpMsgDesc (Error Message reason phrase details like Bad Request, Not Found, Forbidden) and errorDesc (error description like ‘The request does not contain order number’).
- Error Payload will be prepared in the variable called errorPayload, which will be used in flow [name: api-error-handler-flow]. Note that here we are not modifying the existing original payload and don’t want to restrict the return payload (error payload), which can be modified/upgraded/removed at the API level in case any specific business requirement needs to be satisfied at the API level.
// BAD_REQUEST or VALIDATION
<on-error-continue enableNotifications=”false” logException=”false” doc:name=”On Error Continue” doc:id=”b4bb49f0-f593–4d9a-abd8–17ebecbf05d9″ when=”#[ (error.errorType.identifier == ‘BAD_REQUEST’) or (error.errorType.identifier == ‘VALIDATION’) ]”>
<set-variable value=”#[vars.httpStatus default error.errorMessage.attributes.statusCode default 400]” doc:name=”Set httpStatus 400″ doc:id=”66ffa471–1284–4e68-b6a6–96618ea7185d” variableName=”httpStatus”/>
<set-variable value=”#[‘Bad Request’]” doc:name=”Bad Request” doc:id=”391bafd4-f641–4785–9823-e723e0e578af” variableName=”errorMsg”/>
<flow-ref doc:name=”error-handler:set-error-payload-error-subflow” doc:id=”ef7de133–8058–4d08-b0ea-4a681c483926″ name=”error-handler:set-error-payload-error-subflow” />
</on-error-continue>
// UNAUTHORIZED
<on-error-continue enableNotifications=”false” logException=”false” doc:name=”On Error Continue” doc:id=”ffd605d4–6700–4916–827f-ed9faa8309ae” when=”#[ error.errorType.identifier == ‘UNAUTHORIZED’ ]”>
<set-variable value=”#[vars.httpStatus default error.errorMessage.attributes.statusCode default 401]” doc:name=”Set httpStatus 401″ doc:id=”36f7e853–8199–468d-8734–39068daded15″ variableName=”httpStatus”/>
<set-variable value=”#[‘Unauthorized’]” doc:name=”Unauthorized” doc:id=”e8186473-bb97–493a-8395-b0b285fb265f” variableName=”errorMsg”/>
<flow-ref doc:name=”error-handler:set-error-payload-error-subflow” doc:id=”4aa73850-addc-4196-a4eb-3ce982fd924d” name=”error-handler:set-error-payload-error-subflow” />
</on-error-continue>
// ANY (Any Error which are not being caught in earlier on error continue block)
<on-error-continue enableNotifications=”false” logException=”false” doc:name=”On Error Continue” doc:id=”c2472cc5–81a8–422c-9e3a-168f7ac889c3″ type=”ANY”>
<set-variable value=”#[vars.httpStatus default error.errorMessage.attributes.statusCode default 500]” doc:name=”Set httpStatus 500 (ANY)” doc:id=”8c161625–1d5c-48aa-b012-d76bf95caebc” variableName=”httpStatus”/>
<set-variable value=”#[‘Server Error’]” doc:name=”Server Error” doc:id=”7ba8a41a-cf3a-41f9-b85b-3c6a0c38c259″ variableName=”errorMsg”/>
<flow-ref doc:name=”error-handler:set-error-payload-error-subflow” doc:id=”2ee46eab-bdfe-4d2f-9a89-f396390ddb7f” name=”error-handler:set-error-payload-error-subflow”/>
</on-error-continue>
<sub-flow name=”error-handler:set-error-payload-error-subflow” doc:id=”b24ae43d-bf1a-4b82-ad9f-6e1cdbf259b1″ >
<ee:transform doc:name=”Prepare ErrorPayload” doc:id=”0cac7768–78a8–4198-ac6d-c7daf633a8cf” ><ee:message >
</ee:message>
<ee:variables >
<ee:set-variable variableName=”errorPayload” ><![CDATA[
%dw 2.0
output application/json
import * from dw::Runtime
— –
{
“correlationId” : ( correlationId ),
“timeStamp” : now(),
“code” : vars.httpStatus,
“reasonPhrase” : vars.errorMsg,
“description” :
if( !isEmpty(vars.errorDesc))
( vars.errorDesc )
else (
if ( !isEmpty(error.errorMessage.payload) and (typeOf(error.errorMessage.payload) ~= ‘String’) ) (
( error.errorMessage.payload )
) else if ( !isEmpty(error.errorMessage.payload) and (typeOf(error.errorMessage.payload) ~= ‘Object’) ) (
if ( error.errorMessage.payload.description? )
( error.errorMessage.payload.description )
else
( error.errorMessage.payload )
) else if ( !isEmpty(error.errorMessage.payload) )(
( error.errorMessage.payload )
) else if ( isEmpty(error.errorMessage.payload) )(
( error.detailedDescription default vars.errorMsg )
) else (
vars.errorMsg
)
)
}
]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref doc:name=”api-error-handler-flow” doc:id=”ee6adb65–63ad-4db1-ba1f-71b0ffaf4a31″ name=”api-error-handler-flow”/>
</sub-flow>
Conclusion
This will be useful when you have green field implementation or re-designing your APIs to standardise error payload, and consistency, reduce efforts for API governance, quick implementation, and Demo/PoC for API-led connectivity on the MuleSoft Anypoint Platform.