Design – Where would you handle exceptions: controller, service, repository

designexceptionsobject-oriented-design

I have been having this dilemma from time to time whenever I have to design or start a new project from scratch.

I particularly like how Spring framework is organised in terms of classic software patterns like services, repositories, etc. By reading and going through community projects written in Spring and for example Node.js (two absolutely different environments), the APIs differ in several points but the one this post is about is exception handling.

Imagine we are hitting a specific resource by HTTP request. It will first go to the controller, assuming no middleware in place, service, repository if any and back to the controller. But what happens if the service needs to throw an error or the custom implementation of the repository needs to throw an error?

I have seen implementations of both types:

  1. Inside the service/repository, throwing the actual HTTP error wrapped in a try catch statement like: new BadRequestException(statusCode, message)
  2. Let the exception or error bubble up until a global middleware catches it, and returns it back to the client.

I personally believe that mixing the logic layer (service) and data layer-ish (repository) with the transport layer (HTTP controller) is a mistake or a wrong choice since you are mixing conceptually different things. And on the latter, reliance on a global middleware will make you specify the status code or instantiate a class that internally uses the needed status code and message, again, no point on there.

In frameworks or environments that different such Spring and Node.js, I've seen both implementations, especially on Node.js. Nest uses more like a Spring approach and Express more like the second one.

What would you rather do or which is your preferred way of handling this exception handling?

Best Answer

Our Spring project is designed to have a top level handler (with @ControllerAdvice and @ExceptionHandler) that makes sure that all kinds of exceptions are handled in some sane way. This allows us to have specific exceptions that map to HTTP codes, such as UnrecoverableException which will bubble up and return a 500 Internal Server Error.

Low level exception handlers can then decide whether an exception from a 3rd party or other code means that the server is most likely incapable of servicing requests, or whether it might be a temporary issue (at which point a RecoverableException -> 503 Temporarily Unavailable is thrown).

So the general exception handling becomes straight-forward, if we can't do anything about an exception, we map it to a suitable "supertype" and let it go upwards. The supertype exceptions can then contain a lot of additional information for things like logging purposes, method parameters that caused the exception, the current state of things etc.

It works well in our architecture, and keeps exception handling localized. Either you handle the exception, or you send it to the top. But you avoid sending it to the direct parent in the classic "well I can't handle this, but I hope my caller can" way (well not strictly, but it's not so much of a guess where a specific exception might get handled).


Spring also provides a way to map an exception directly to a HTTP status, with @ResponseStatus in the exception class, but what if we're not using HTTP? With our solution you can create a gateway specific @ExceptionHandler because the exception only describes the state of the system and intent of the error, not what the end result should be.

This is of course Spring specific, but it may be achievable in other languages / frameworks.

Related Topic