Dmitry Shpika // scriptin
type Links = GitHub | LinkedIn | YouTube | StackExchange | NPM;

RESTful HTTP: error handling

Published on 08 Feb 2015

8 mins read

Error handling in web services (REST, JSON-RPC, etc.) is a process of catching exceptions, logging them properly, and translating them into HTTP-compliant responses with human-readable error messages:

I use Java in my examples, but this should be similar for any decent OOP language.

Naive error handling and common mistakes

Now, onto fixing that.

Logging

My favourite logging framework in Java is Logback, so I will use severity levels it uses: TRACE, DEBUG, INFO, WARN, ERROR. If you don’t understand what are those and why they exist, please read the docs before you continue.

You should always log stack traces for exceptions which you code does not know how to handle. It is typical that you also respond with 500 Internal Server Error in such cases, because this should be considered like something truly unexpected. For those exceptions you handle, you can just log error message, and provide stack trace as a debugging information.

ERROR, WARN, and INFO must be logged into your primary log. As for DEBUG and TRACE messages, don’t put them there to prevent disk consumption issues. Use syslog or similar mechanisms instead, or at least separate properly rotated log. You should be able to turn off debugging log without restarting your application.

Exception handling layers

Read this article first: Logging Exceptions: Where to Log Exceptions?

I know you’ve skipped it, so here’s a summary: there are different layers in your code on which you can potentially log exceptions. Mr. Jenkov uses 3, but I’ll split the “top” one since this article is specifically about web services:

Bottom and middle layer

By saying “domain-specific” I mean related to business domain (area of knowledge), not like in “domain name of a web site”.

API implementation layer

API-specific exceptions are different from domain-specific exceptions because the latter can be used elsewhere. Also API-specific exceptions should help you determining which response status and message should be presented to a client. Typically you just display error messages from those exceptions to a client, so they should be clear. For example:

// Assume that all those exception classes
// extend some base class `ApiException`

throw new ApiNotFoundException(
    404,
    "Entity with id=" + id + " not found."
);

throw new ApiDatabaseUnavailableException(
    502,
    "Database server is unavailable. Retry later."
);

throw new ApiConstraintViolationException(
    422, // WebDAV, Unprocessable Entity
    "Entity with id=" + id + " cannot be deleted " +
    "because it is a parent of entity with id=" + childId + ". " +
    "Delete all children first."
);

Error interceptor/wrapper/handler layer

Code might look like this:

    // ...
} catch (ApiException e) {
    // Caught ApiException or one of its subclasses
    // Can safely use its error message and status
    response.setStatus(((ApiException) e).getStatus());
    response.setMessage(e.getMessage());
} catch (DomainMailServerUnavailableException e) {
    // Caught a domain-specific exception
    // which says that mail server is down
    response.setStatus(502);
    response.setMessage("Mail server is unavailable. Retry later.");
} catch (DomainAccessDeniedException e) {
    // Caught another domain-specific exception
    response.setStatus(503);
    response.setMessage(
        "You have insufficient privileges to perform this action."
    );
} catch (Exception unhandledException) {
    // Catch-all case:
    // 1. Log the exception
    logger.error(
        "Unhandled exception!",
        unhandledException
    );
    // 2. Notify devs/admins
    systemAlarm.unhandledException(unhandledException.getMessage());
    // 3. Show a generic error to users
    response.setStatus(500);
    response.setMessage(
        "Sorry, something is broken. We're looking into that."
    );
}

Error handling flow

The flow of exception handling should generally look like this:

  1. Throwing/encountering general-purpose exceptions.
  2. Wrapping them into domain-specific exceptions. Or handling, if possible.
  3. Wrapping those into API-specific exceptions if necessary. Or handling, if possible.
  4. Building API responses with human-readable error messages you get from domain and API layer exceptions.

There are rare cases where wrapping in steps 2 and/or 3 can be skipped. Think carefully.

Custom error codes

Sometimes HTTP statuses are just not enough to explain the meaning of all possible errors, that’s why we also provide messages. However, clients may need to localize those messages or take some other actions depending on what’s happened, and keeping a list of all possible error messages on a client is not a right thing to do.

Many APIs provide their custom error codes in addition to HTTP statuses:

As you can see, those codes provide the same information as messages do, but are more convenient to handle when you need to take actions or display custom text.

Summary

DO:

DO NOT: