Skip to main content

Integration & IT Modernization

Mule API Exception Handling Patterns

Unlike regular Mule applications, when a new RAML based Mule API project is generated, the APIKit tool will create a global exception handler. Although this default exception handler covers some basic HTTP 400-level errors, it is only a starting point for a comprehensive error handling strategy. More can be done to enhance the error handling for the API application.

This post will explore two common patterns to enhance the exception handling for an API application.

The Default API Exception Handler

MuleSoft API exception handling is built on top of the general MuleSoft error handling framework.

When a Mule API project is generated, the Mule APIKit creates a global exception handler with a name like “apiKitGlobalExceptionMapping.” This exception handler is referenced by the API main flow, which contains the “APIKit Router.” All API calls are routed through the API main flow and the “APIKit Router.”

The following code snippet shows the structure of the API exception handler:

<apikit:mapping-exception-strategy name="api-apiKitGlobalExceptionMapping">
<apikit:mapping statusCode="404">
<apikit:exception value="org.mule.module.apikit.exception.NotFoundException" />
<set-property propertyName="Content-Type" value="application/json" doc:name="Property"/>
<set-payload value="{ &quot;message&quot;: &quot;Resource not found&quot; }" doc:name="Set Payload"/>
</apikit:mapping>
<apikit:mapping statusCode="405">
<apikit:exception value="org.mule.module.apikit.exception.MethodNotAllowedException" />
..
</apikit:mapping>
<apikit:mapping statusCode="415">
<apikit:exception value="org.mule.module.apikit.exception.UnsupportedMediaTypeException" />
...
</apikit:mapping>
<apikit:mapping statusCode="406">
<apikit:exception value="org.mule.module.apikit.exception.NotAcceptableException" />
...
</apikit:mapping>
<apikit:mapping statusCode="400">
<apikit:exception value="org.mule.module.apikit.exception.BadRequestException" />
...
</apikit:mapping>
</apikit:mapping-exception-strategy>

When an exception is triggered anywhere within the API application, two things may happen: 1) if the exception is already defined (mapped) in the default exception handler, the client will receive the predefined HTTP error code along with the message. For example, “404 / Page not found”. 2) If the exception is not already defined in the handler, for example, if a SQL server connection exception is thrown within the application, the exception will bubble up to the default handler. The default exception handler will respond with error code 500, and the #[exception.message].

Mule API Exception Handling Patterns

This default exception handler “apiKitGlobalExceptionMapping” is a good foundation, but it has some shortcomings:

  • Since any exceptions not defined in the pre-specified exception list are lumped together as HTTP 500 error, it does not provide enough information to the client. There is no customized response per each particular exception.
  • On the other hand, any undefined exception will return #[exception.message] as payload, which may contain too much information. For example, in the case of SQL exception, the error message may contain JDBC URL connection parameter, which may include host, port, instance id, username etc. The error message may even include the actual SQL statement. This can be a security concern.

Besides setting HTTP code and payload, the default handler does not do anything else. There is not even logging entry by default. This can make troubleshooting the problem more difficult.

The remainder of this post will show how we can create a more comprehensive API error handling strategy on top of the default handler.

Pattern 1 – Extend the Default Exception Handler

The first pattern extends the default handler.

There are several enhancements that can be added to extend the default handler:

  • Add more exception cases. For example, developers can add

java.sql.SQLException => 4xx (or use a 5xx code)

The code snippet may look like:

<apikit:mapping statusCode=“4xx">

<apikit:exception value=" java.sql.SQLException " />

logging code here, set content type, payload etc accordingly…

</apikit:mapping>

The 4xx or 5xx is code should come from either a well-defined API design document or the API RAML

  • Add logging entries to each exception case. At the minimum, the log entries will be helpful when developers need to troubleshoot the exception. Additionally, the logging entries allow developers to add certain keywords for each project. These keywords can be used by other data monitoring or data mining tools, such as Splunk.
  • Add more exception handling actions per each project requirements, such as alert email, retry logic etc.
  • Add a catch all entry using java.lang.Exception.

By doing this, developers can set a different HTTP status, such as 599. Developers can also control whether to expose #[exception.message] to the API client. The following code snippet shows an example of catch-all exception handling:

<apikit:mapping statusCode=“599">

<apikit:exception value=" java.lang.Exception" />

<!—add logging entries, set content type, payload, and take other actions per each project requirements accordingly -->

…

</apikit:mapping>

In this case, the “599” code can differentiate that the error comes from the API application, not from the HTTP server stack. Because HTTP 500 status code is designed for generic unknown errors from the HTTP server.

Advantages of Pattern 1:

  • The main advantage of extending the default handler is simplicity. Developers can simply edit the default API exception handler, adding more exception entries per each project. The exception handling code is in one place, this can be an advantage for a project with few exception cases.

Disadvantages of Pattern 1:

  • The disadvantage is all exception cases are crammed in one place when there are many exception cases. The code can become unyielding and hard to read.
  • The exception handler and the location where the exceptions actually happen may not be in the same file. It may require reader to jump from place to place to understand the flow and the exception handling logic.
  • Because the exception handler is global, it is hard to create localized and more elaborate exception handling. For example, when adding the exception entry for java.sql.SQ:Exception at the global level, it is difficult to make distinction between SQL read exception from a SQL connection exception.

Pattern 2 – Add Custom Exception Handlers

Custom exception handler(s) can be added by using the Mule exception handling strategies. The best practice is to add global exception handling strategy and let flows reference the global strategy. The custom exception references should be added in the flows that are directly called by the “apiRouter”. These flows are generated by the APIKit, such as “get:/person:api-config”. These are the “resource path flows”, because each flow represents the REST resource path as defined in the API RAML. By following this pattern, the HTTP status code and exception message will be picked up by the API main flow and returned to the client.

Mule API Exception Handling Patterns

When working with Mule API custom exception handling, developers need to be aware of two facts:

  1. Because this is an API project, the exception handler needs to set the HTTP error status and content-type in additional to setting the error message.
  2. Mule API project has a single API main flow which contains the “apiRouter”. This API main flow acts like a Java main().This main flow has an associated default exception handler generated by APIKit when the API project was first created. Any API responses will be returned by this main flow or the associated default exception handler. This unique application structure has consequences that affect the custom exception handling.

If a custom exception handler handles an exception in a flow, this exception is considered as “consumed”. The “consumed exceptions” will no longer be visible to the calling flow. Therefore, after the custom exception handler sets the HTTP code and message, these values will be returned by the API main flow to the API client.

Custom exception handler can re-throw exceptions. When that happens, the default exception handler in the main flow will catch the exception and handle it accordingly.

If additional exception handling is needed below the “resources path flow” level, these sub-level exception handlers should not be used to set the HTTP status and the final response messages. Because these values can be potentially reset by the intermediate flows in the flow invocation chain.

Finally, even with custom exception handler(s), the default global exception handler should still add the “catch-all” exception case for “java.lang.Exception”. Please reference the previous section for details.

Advantages of Pattern 2:

  • Custom exception handler(s) allow developers to modularize the exception handling instead of using a single global exception handler. It can make code easy to understand and maintain.
  • It can add more elaborate exception handling actions. For example, for JDBC operations, there can be DB connection error, DB read or write errors. Each of these operations may throw java.sql.SQLException, a custom exception handler can try to differentiate each type of operation, and add more details to the exception message to indicate if it is DB read, write, or DB connection. The exception handler can further set a different HTTP status code for each exception. This status code should be based on a well-thought-out API design document or the API RAML of the project.

Disadvantages of Pattern 2:

  • If not used properly, it can result in too many exception handlers and causes confusion.
  • Requires deeper understanding of how the exception handling chain works. Developers need be disciplined to add the custom exception handler(s) at the proper level.

Going Beyond the Basics

To enhance the API exception handling, a developer can perform more than the two exception handling patterns discussed the previous sections.

Technically, developers can add any number of exception handler(s) and at any levels of the flows. However, care must be given such that HTTP status and error message can be propagated to the API main flow and returned to the calling client as intended.

Besides requiring HTTP status code, API exception handling is just like standard Mule exception handling. The custom exception handler can take any actions like a standard exception handler. In theory, when an exception happens, an exception handler may take any of these potentially actions:

  1. do nothing (ignore, “swallow”)
  2. logging
  3. direct technical retry (add delay then retry), business retry (may require manually rehab the data and retry)
  4. alert (send message to messaging system, such Email, JMS etc)
  5. abort (terminate)
  6. re-throw (bubble up)
  7. rollback
  8. delegate (which can do any of the above #1 – 7, and more)

For example, #1 can be justified when an application needs to do a “best of effort” to invoke a web service for notification purpose. If calling the web service causes an exception, the application can simply ignore the error and moves on.

These additional actions are beyond the basic exception handling patterns, developers will need to follow the design requirements and add proper actions per each project requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Follow Us