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?

  • If your services and repositories are throwing errors with knowledge about the transport layer, there's no point separating the transport layer. The service should throw a business-level error, then the transport layer turns that into e.g. an HTTP response. This doesn't have to be a global middleware, it could just be try in a method in the controller (or see e.g. baeldung.com/exception-handling-for-rest-with-spring for some options when that needs to be across multiple methods). – jonrsharpe Jun 14 '19 at 7:03
  • All your efforts of "decoupling" or "abstracting" one layer from another or all your efforts on separating concerns are just a waste of time if you implement #1. If Repositories are meant to be agnostic to all other layers, why it should be aware of HTTP status and other HTTP stuff? Why it does know it was called from a controller? It should not even know that the whole transactions belong to a higher abstraction named HTTP request. It does not make much sense IMHO. – Laiv Jun 14 '19 at 7:28
  • @Laiv totally agree. The fact of picturing an example in the repository layer was an extreme scenario where as you point, it doesn't make any sense at all. It's just stating the fact that I've seen that approach on community projects – Javier Rodriguez Jun 14 '19 at 7:32
  • It could make sense if, as Springs automagically allows you, you decide to turn your repository interfaces into the REST interface too. This is possible through the Spring data project. In such a scenario, you don't declare controllers. These are created automagically by spring. But then, the question is not which error handling strategy is preferable. The question is "why such a design?". – Laiv Jun 14 '19 at 7:49

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.