The Case for Standardized Error Handling in Your Web Application’s APIs
Tags: api, errors, programming
Published: 2023-03-25 22:05:16

---
title: The Case for Standardized Error Handling in Your Web Application’s APIs
published: true
date: 2019-02-03 00:00:00 UTC
tags: api,errors,programming
canonical_url: https://www.paigeniedringhaus.com/blog/the-case-for-standardized-error-handling-in-your-web-applications-apis
---

[![Error alert box with success as the message](https://www.paigeniedringhaus.com/static/6bfebbb5cf8410b2ed7343d385648f8b/a4aa0/error-hero.webp "Error alert box with success as the message")](/static/6bfebbb5cf8410b2ed7343d385648f8b/a4aa0/error-hero.webp)

## Introduction

Bad error messages are a sin we’re all guilty of as programmers. On the server side, on the client side, they are rampant basically everywhere in web development.

I write them. You write them. My whole team writes them. We all agree they’re bad: the error messages don’t tell us or our users what the real problem is and only through digging deep into the codebase and tracing through application logs can we locate the actual culprit — most of the time.

This is not a new or unusual phenomenon. It’s standard, and it makes everyone’s lives infinitely more painful than they need to be. But every time we talk about how to make the error messaging better — more verbose, more pinpointed, less nebulous about what actual error occurred, these efforts to improve error messaging get deprioritized by whoever has the power to direct the dev team and we move on to more straightforward, end-user-benefitting features.

And the vicious cycle continues. A user encounters an error on client side of the application, the development team is contacted after the error can’t be resolved by turning the system off and on again (see meme below), the devs look through tons of logs and try to reproduce error locally (which is sometimes an impossible feat), the team either reproduces the error and figures out what caused it and how to fix it, or they can’t reproduce the error and close the bug with no fix, until it rears its ugly head again in the future.

[![Have you tried turning it off and turning it back on again?](https://www.paigeniedringhaus.com/static/8aa6fc923fb106bfa034bd5dd1db73f1/487e2/it-crowd.webp "Have you tried turning it off and turning it back on again?")](/static/8aa6fc923fb106bfa034bd5dd1db73f1/487e2/it-crowd.webp)

_I couldn’t resist the opportunity to use this meme — it’s so accurate sometimes it’s frightening._

Does this sound like an optimum system to you? Me neither. Which is why I’m here, writing to you today.

**I’m making the case for standardizing error handling from APIs so we can all stop wasting so much time tracking down bugs, and get back to the coding we all want to do — building cool things that make people’s lives better.**

---

## Standardized errors: the need & the ground rules

[![Typically unhelpful error message.](https://www.paigeniedringhaus.com/static/862ae158d393895da521d69c6af4e1c8/8efd0/unhelpful-error-message.webp "Typically unhelpful error message.")](/static/862ae158d393895da521d69c6af4e1c8/7f403/unhelpful-error-message.webp)

_A standard, totally unhelpful, API error message example._

In case you didn’t read the error message above, the part that matters says: `Failed to execute ImportKey action in set service. Deleting created cart item. : 500 Internal Server Error`.

Yes, this is an actual error message from one of my team’s actual backend web applications. No, it doesn’t make any sort of sense; I totally agree with you.

In web development, we place a lot of emphasis on the “happy path”. The path that we want and expect our users to take when they’re using our tool or application — it’s the path we code for, the path we optimize for, the path we place most of our focus on.

The “unhappy path”, is the path when a user doesn’t do the right thing, click the right button, or generally figures out how to use the system in some way other than its intended purpose. This is the path where things go wrong, and with bad error messaging (like the above), we, as both users and developers, have a really hard time figuring out what exactly went wrong. And in general, the unhappy paths (and the errors they produce) are given a lot less thought about how they should be handled in an effective manner.

That needs to change. Here’s what I propose in the name of better error handling, all of which is based on the [**OData v4 JSON specification**](http://www.odata.org/documentation/).

### What is OData?

[![OData logo](https://www.paigeniedringhaus.com/static/6a3dca8b8563ebb21b45198f5891bd36/8efd0/odata-logo.webp "OData logo")](/static/6a3dca8b8563ebb21b45198f5891bd36/446b5/odata-logo.webp)

Before I give my recommendations, let me give a little background on OData.

**OData** stands for Open Data Protocol, and it is an [open protocol](https://en.wikipedia.org/wiki/Protocol_(computing)) which allows the creation and consumption of queryable and interoperable [RESTful APIs](https://en.wikipedia.org/wiki/RESTful) in a simple and standard way. [Microsoft](https://en.wikipedia.org/wiki/Microsoft) initiated OData in 2007. — [Wikipedia, OData](https://en.wikipedia.org/wiki/Open_Data_Protocol)

In essence, OData defines a set of best practices for building and consuming RESTful APIs (application program interface). These practices help developers focus on business logic while building RESTful APIs without having to worry about the various approaches to define things like request and response headers, status codes, HTTP methods, URL conventions, media types, payload formats, query options and more.

It proposes standards to make web development a little less haphazard and a little more predictable, regardless of programming language or development approach and team.

Now, we know about OData and its legitimacy and reason for being in the world, let’s get down to the rules of handling errors.

### 8 error rules to follow

Below are the standards my team and wider company are working to enact with our applications — they’re generic enough recommendations to apply broadly to APIs in general, but specific enough to remove the questions around how to go about implementing the recommendations.

**Rule 1: Code one, reusable error handler**

- For nonsuccess conditions, developers _should_ be able to write one piece of code that handles errors consistently across different REST API methods.
- This allows for the building of simple and reliable infrastructure to handle exceptions as a separate flow from successful responses.
- Keep in mind, this error handler is very generic and does not require specific OData constructs. APIs _should_ use this format even if they are not using other OData constructs.

**Rule 2: The JSON error handler must have an `error` object**

- The error response _must_ be a single JSON object. This object _must_ have a name/value pair named `error`. The value _must_ be a JSON object.

[![Basic error object example](https://www.paigeniedringhaus.com/static/9f74ee8182215ab8b08721b4db72aaa2/8efd0/basic-error.webp "Basic error object example")](/static/9f74ee8182215ab8b08721b4db72aaa2/7f403/basic-error.webp)

_Example of the most basic `error` object._

**Rule 3: `error` must contain `code` and `message`, and may contain other properties For more information**

- This object _must_ contain name/value pairs with the names `code` and `message` and it _may_ contain name/value pairs with the names `target`, `details`, and `innererror`.
- The value for the `code` name/value pair is a language-independent string. Its value is a service-defined error code that _should_ be human-readable.
- This code serves as a more specific indicator of the error than the HTTP error code specified in the response.
- API Methods _should_ have a relatively small number (about 20) of possible values for `code` and all clients _must_ be capable of handling all of them.
- Most services will require a much larger number of more specific error codes, which are not interesting to all clients. These error codes _should_ be exposed in the `innererror` name/value pair as described below.
- Introducing a new value for `code` that is visible to existing clients is a breaking change and requires a version increase. API Methods can avoid breaking changes by adding new error codes to `innererror` instead.

[![Multi property error object example](https://www.paigeniedringhaus.com/static/cc256b4de301236af211018ce20ca4db/8efd0/multi-prop-error.webp "Multi property error object example")](/static/cc256b4de301236af211018ce20ca4db/7f403/multi-prop-error.webp)

_Example of the `error` object with multiple properties included._

**Rule 4: The `message` should help humans debug the error**

- The value for the `message` name/value pair _must_ be a human-readable representation of the error. It is intended as an aid to developers and is not suitable for exposure to end users.
- API methods wanting to expose a suitable message for end users _must_ do so through an annotation or custom property.
- API methods _should not_ localize `message` for the end user, because doing so _may_ make the value unreadable to the app developer who may be logging the value, as well as make the value less searchable on the Internet.

**Rule 5: `target` is the name of the property in error**

- The value for the `target` name/value pair is the target of the particular error (e.g., the name of the property in error).

**Rule 6: `details` is an array of objects with `code` and `message`**

- The value for the `details` name/value pair _must_ be an array of JSON objects that _must_ contain name/value pairs for `code` and `message`, and _may_ contain a name/value pair for `target` as described above. The objects in the `details` array usually represents distinct, related errors that occurred during the request. See example below.

Example of the `details` object:

```json
{   
  "error": {     
    "code": "BadArgument",     
    "message": "Multiple errors in ContactInfo data",     
    "target": "ContactInfo",     
    "details": [       
      {         
        "code": "NullValue",         
        "target": "PhoneNumber",         
        "message": "Phone number must not be null"       
      },       
      {         
        "code": "NullValue",         
        "target": "LastName",         
        "message": "Last name must not be null"       
      },       
      {         
        "code": "MalformedValue",         
        "target": "Address",         
        "message": "Address is not valid"       
       }     
     ]   
   } 
 }
```

In this example there were multiple problems with the request, with each of the individual errors listed in `details`.

**Rule 7: `innererror` is an object with service-defined contents**

- The value for the `innererror` name/value pair _must_ be an object.
- The contents of this object are service-defined. API methods wanting to return more specific errors than the root-level code _must_ do so by including a name/value pair for `code` and a nested `innererror`.
- Each nested `innererror` object represents a higher level of detail than its parent.
- When evaluating errors, clients _must_ traverse through all of the nested `innererrors` and choose the deepest one that they understand. This scheme allows services to introduce new error codes anywhere in the hierarchy without breaking backwards compatibility, so long as old error codes still appear.
- The service _may_ return different levels of depth and detail to different callers. For example, in development environments, the deepest `innererror` _may_ contain internal information that can help debug the service.
- To guard against potential security concerns around information disclosure, services _should_ take care not to expose too much detail unintentionally.
- Error objects _may_ also include custom server-defined name/value pairs that _may_ be specific to the code. Error types with custom server-defined properties _should_ be declared in the service’s metadata document. See example below.
- Error responses _may_ contain annotations in any of their JSON objects.

[![innererror object example](https://www.paigeniedringhaus.com/static/42486e35b8c530eb329731a78c869de1/8efd0/inner-error.webp "innererror object example")](/static/42486e35b8c530eb329731a78c869de1/7f403/inner-error.webp)

_Example of `innererror` object._

Example of `innererror` object:

```json
{   
  "error": {     
    "code": "BadArgument",     
    "message": "Previous passwords may not be reused",     
    "target": "password",     
    "innererror": {       
      "code": "PasswordError",       
      "innererror": {         
        "code": "PasswordDoesNotMeetPolicy",         
        "minLength": "6",         
        "maxLength": "64",         
        "characterTypes": ["lowerCase","upperCase","number","symbol"],         
        "minDistinctCharacterTypes": "2",         
        "innererror": {           
          "code": "PasswordReuseNotAllowed"         
        }       
      }     
    }   
  } 
}
```

In the example above, the most basic error code is `BadArgument`, but for clients that are interested, there are more specific error codes in `innererror`.

The `PasswordReuseNotAllowed` code may have been added by the service at a later date, having previously only returned `PasswordDoesNotMeetPolicy`.

Existing clients do not break when the new error code is added, but new clients _may_ take advantage of it. The `PasswordDoesNotMeetPolicy` error also includes additional name/value pairs that allow the client to determine the server’s configuration, validate the user’s input programmatically, or present the server’s constraints to the user within the client’s own localized messaging.

**Rule 8: Code for failure with retries**

- It is recommended that for any transient errors that may be retried, services _should_ include a `Retry-After` HTTP header indicating the minimum number of seconds that clients _should_ wait before attempting the operation again.

And This concludes the rules to a standardized API error handling strategy. It’s not overly complicated, but it recommends good guidelines to try to adhere to when encountering the unhappy path.

---

## Conclusion

Error messaging isn’t the most fun thing to implement, but it’s even less fun to try and debug a painfully vague (and oftentimes misleading) server message. Standardized error handling is a must — especially if you’re part of a larger team of developers, or providing an API service to external clients.

Organizations like OData have helped establish best practices around writing and using APIs, which helps developers focus on the business logic of their applications instead. By following these best practices, we’ve established a set of easy to understand and repeatable rules for error handling to make the whole process of dealing with the inevitable unhappy path simpler.

Check back in a few weeks, I’ll be writing about React or something else related to web development, so please follow me so you don’t miss out.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading, I hope this helps you more effectively handle errors in a standardized format so you can better catch and fix bugs.

---

## References & Further Resources

- [OData V4 JSON Specifications](https://www.odata.org/documentation/)
- [Open Data Protocol](https://en.wikipedia.org/wiki/Open_Data_Protocol), Wikipedia
- [OData](https://www.odata.org/) docs