Giovanni Silva
Giovanni Silva

Giovanni Silva

Advance Exception Handler

Giovanni Silva's photo
Giovanni Silva
·Aug 13, 2019·

12 min read

As developers we face the possibility of errors all the time. One of the most common ways things can go wrong is when a exception is throw.

Some people believe that exceptions are a broken concept. I will not enter this discussion, instead, I will present a strategy that works great in every project I have the pleasure to have a voice on it (yes, some companies culture are broken). Is currently being implemented on a project and with a plan to became a drop-in library. I also use this extensively on personal projects, so, it is battle tested. Is surprise simple but sophisticated at the same time and can be summarized in one phrase:

Do not handle exceptions if you are not recovering from it in other words: if is not a case you are recovering, trying again, following other path, doing some business logic simple don't do it

Even log is not recovering, if you code just log the exception, please remove it.

Now, you may be wondering, but I do need, at minimum, to present meaningful messages for the user. That is an excellent question, the solution is elegant and covers this use case.

The solution

The follow principle, can be used in many platforms and languages:

  • Handle exception in a central way. Let all flow to the boundary on which you can catch by type. In a backend web application, that boundary will be the controller layer. In a frontend application, that boundary will be the requests.
  • Interceptors are king here.

In Java, we can intercept method calls, this is called Aspect Orienting Programming. We can use frameworks like Spring and CDI to do exception handling in a very elegant way.

In the frontend we can use http interceptors for network errors and messages, combined with browser based javascript exception handling (global handling).

Today I will talk about Spring and CDI, and how you can organize your exceptions to take full advantage of their features. The combination of frontend based exception handling and backend based exception handling is a full strong solution for web based applications.

Spring framework based backends

Spring offers more then one way to do that. The best for a REST application (which is ubiquitous these days), is the ControllerAdvice. We want to return appropriated HTTP requests, handling your exceptions.

Let's improve the follow code with the ControllerAdvice technique.

    @PostMapping("/{graphId}")
    public ResponseEntity findDistanceOnPath(
        @PathVariable("graphId") Long graphId,
        @RequestBody PathDTO pathDTO) {

        if (pathDTO.getPath().isEmpty()) {
            return ResponseEntity.badRequest().body("Path cant be empty");
        }

        try {
            List<DistanceInput> distanceInputs = findAndConvertGraph(graphId);
            String path = String.join("-", pathDTO.getPath());
            GraphProcessor processor = new GraphProcessor(distanceInputs);
            try {
                long distance = processor.calculateRouteDistance(path);
                return ResponseEntity.ok(new DistanceDTO(distance));
            } catch (NoSuchRouteException e) {
                return ResponseEntity.ok(-1);
            }
        } catch (NoSuchGraphException ex) {
            return ResponseEntity.notFound().build();
        }


    }

    @PostMapping("/{graphId}/from/{from}/to/{to}")
    public ResponseEntity findDistanceBetweenTowns(
        @PathVariable("graphId") Long graphId,
        @PathVariable("from") String from,
        @PathVariable("to") String to) {
        try {
            List<DistanceInput> distanceInputs = findAndConvertGraph(graphId);
            GraphProcessor processor = new GraphProcessor(distanceInputs);
            GraphStrategy strategy = new DijkstraStrategy(processor);
            strategy.execute(from);
            try {

                List<Vertex> shortestPath = strategy.getShortestPath(to);
                long shortestDistance = strategy.getShortestDistance(to);
                List<String> path = shortestPath.stream().map(Vertex::getCity).collect(Collectors.toList());
                DistancePathDTO response = new DistancePathDTO(shortestDistance, path);
                return ResponseEntity.ok(response);
            } catch (NoSuchRouteException ex) {
                return ResponseEntity.ok(-1);
            }

        } catch (NoSuchGraphException ex) {
            return ResponseEntity.notFound().build();
        }

    }

The code smell here, is that your catch is just to transform the response. We are not trying bypass the error and do something different, in other words, the exception itself is not being handled.

That is a code you can find everywhere, do note that is repeated in the methods. This situation gets worse as time passes and specially as you go deep in the code layers, where developers usually try to handle the business (valid) and ceremony (log, rethrow, etc..) failure scenarios.

Before introducing the ControllerAdivice which is really simple. Let me show you the tests:

    @Test
    public void findDistanceOnPathCase1() throws Exception {

        // GraphProcessor 1001 is AB5, BC4, CD8, DC8, DE6, AD5, CE2, EB3, AE7
        postDistancePath(new String[]{"A", "B", "C"}, 1001)
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.distance").value(is(9)));
    }

    @Test
    public void findDistanceOnPathCase5() throws Exception {

        postDistancePath(new String[]{"A", "E", "D"}, 1001)
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").value(is(-1)));
    }

    @Test
    public void findDistanceOnPathCaseNotFound() throws Exception {

        postDistancePath(new String[]{"A", "D"}, -1)
                .andExpect(status().isNotfound());
    }

I love tests, I think is part of the development and not "a thing to add". If you don't do it automatically you do it manually and then you are losing so much time. Is incredibly every time a hear someone making the case against automated tests, saying is a waste of time, or it will take more resources, these people don't know what they are talking about. But that is not the focus of this blog post.

Now, lets introduce the controller advice, refactor the code and rerun the tests.

    @ControllerAdvice
    public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

        @ExceptionHandler(value
                = {NoSuchGraphException.class})
        protected ResponseEntity<Object> noSuchGraph(
                RuntimeException ex, WebRequest request) {
            return handleExceptionInternal(ex, null,
                    new HttpHeaders(), HttpStatus.NOT_FOUND, request);
        }

        @ExceptionHandler(value
                = {NoSuchRouteException.class})
        protected ResponseEntity<Object> noSuchRoute(
                RuntimeException ex, WebRequest request) {
            return handleExceptionInternal(ex, -1,
                    new HttpHeaders(), HttpStatus.OK, request);
        }
    }

Now your code can be rewritten to:

    @PostMapping("/{graphId}")
    public ResponseEntity findDistanceOnPath(@PathVariable("graphId") Long graphId,
                                                @RequestBody PathDTO pathDTO) {

        if (pathDTO.getPath().isEmpty()) {
            return ResponseEntity.badRequest().body("Path cant be empty");
        }

        List<DistanceInput> distanceInputs = findAndConvertGraph(graphId);
        String path = String.join("-", pathDTO.getPath());
        GraphProcessor processor = new GraphProcessor(distanceInputs);
        long distance = processor.calculateRouteDistance(path);
        return ResponseEntity.ok(new DistanceDTO(distance));

    }

    @PostMapping("/{graphId}/from/{from}/to/{to}")
    public ResponseEntity findDistanceBetweenTowns(
        @PathVariable("graphId") Long graphId,
        @PathVariable("from") String from,
        @PathVariable("to") String to) {
        List<DistanceInput> distanceInputs = findAndConvertGraph(graphId);
        GraphProcessor processor = new GraphProcessor(distanceInputs);
        GraphStrategy strategy = new DijkstraStrategy(processor);
        strategy.execute(from);

        List<Vertex> shortestPath = strategy.getShortestPath(to);
        long shortestDistance = strategy.getShortestDistance(to);
        List<String> path = shortestPath.stream().map(Vertex::getCity).collect(Collectors.toList());
        DistancePathDTO response = new DistancePathDTO(shortestDistance, path);
        return ResponseEntity.ok(response);


    }

The tests will all be green. Have you noticed the difference in how we read the code? Is much cleaner to maintain. Time is valuable, and we have gain but time and perceived quality with this simple approach.

Now we can with confidence, write code in all other controllers that will benefit from this.

Why quality? A central handler will not leave unmanaged exceptions to throw in the face of the user, even if the developer forgot or lack will power (yes, handling exceptions all the time make us tired and is error-prone).

Your controller advice handles only business exceptions, and it should handle generic and framework exceptions as well. You can just catch the RuntimeException class, or DataIntegrityViolationException for framework data access, or even module specific exceptions.

For this, I do:

  1. I organize my exceptions on a hierarchical that starts with a ApplicationNameException, where ApplicationName is the name of project.
  2. I add valuable information on the exception message when I create it, and valuable properties to more specific exceptions. That way the handling becomes easier. Take the DataIntegrityViolationException as an anti example of what I just say, it is hard to know why on earth is happening (notnull?, unique? which property? How the user call this property?). So in the essence be specific. If you find useful create more exceptions.
  3. The message is not in code, but in message properties and easier for internationalization.

Exception hierarchy

This is taken directly from my new project called Payment Gateway. Is a microservices like architecture. One of its components is the PaymentGatewayCore wich is responsible for the transactions. The code is in kotlin lang, but is equally valid in java. I'm telling you just to situate.

The hierarchy looks like:

    /**
     * Global exception class to differentiate this system exceptions from class and libraries exceptions
     * @author Giovanni Silva
     */
    open class PaymentCoreSystemException(message: String, cause: Throwable?) : RuntimeException(message, cause)

In payment module:

    class PaymentException : PaymentCoreSystemException {

        constructor(message: String) : super(message, null) {}

        constructor(message: String?, exception: RuntimeException?) : super(message!!, exception) {}
    }

    class GatewayException : PaymentException {

        constructor(message: String,
                    messageParameters: List<String> = emptyList(),
                    cause: Throwable? = null) : super(message, messageParameters, cause)

    }

    class GatewayApiUnauthorizedException(cause: Throwable? = null) :
            GatewayException("{com.kugelbit.payment.core.gateway.exception.apiUnauthorized}", emptyList(), cause)

    class GatewayPaymentNotFoundException(cause: Throwable? = null) :
            GatewayException("{com.kugelbit.payment.core.gateway.exception.paymentNotFound}", emptyList(), cause)

    class GatewayValidationException(val validateMessage: String) : PaymentException("{exception.gateway.validation}", listOf(validateMessage))

Now we can:

Catch all using RuntimeException class. Catch whole application using PaymentCoreSystemException class. Catch a module specific PaymentException class. Catch a external gateway specific problem GatewayException class. A gateway is a external system like Paypal, represented by facade code on the application. Catch just a GatewayApiUnauthorizedException.

I think you got the picture. Let me know in the comment's what you think. I don't think this is bureaucratic, because is created on demand per business domain.

Note: In other project we create just a DomainBusinessException class and no hierarchy. This also works, lets see as time pass how it progress.

How to throw exceptions

If you notice, the more specific exceptions, tend to have a static message, and when it varies, you can parametrize properties.

They are very strait forward to throw:

    @Throws(GatewayApiUnauthorizedException::class, GatewayException::class, GatewayValidationException::class)
    override fun associatePaymentMethod(paymentId: String, customer: Customer, paymentMethod: PaymentMethod): JsonNode {
        val json = createJsonAssociatePayment(customer, paymentMethod)
        try {
            val entity = createHttpEntity(json)
            val response = restTemplate.postForEntity("$apiUrl/v1/charge/$paymentId/pay", entity, String::class.java)
            return objectMapper.readTree(response.body)
        } catch (ex: RuntimeException) {
            when (ex) {
                is HttpClientErrorException, is HttpServerErrorException -> {
                    val exception = ex as RestClientResponseException

                    if (exception.rawStatusCode == 401) { // Unauthorized
                        throw GatewayApiUnauthorizedException(ex)
                    }

                    val errorJson = objectMapper.readTree(exception.responseBodyAsString)

                    val message = extractError(errorJson)

                    throw GatewayValidationException(message)
                }
                else -> throw GatewayException("{exception.gateway.cantCharge}", ex)
            }


        }
    }

Here is perfectly fine to rethrow the exceptions, because we are parsing and looking for the cause and is responsible of this code to throw meaningful exceptions. If we catch the HttpClientErrorException globally this is too generic and we have to remember each possible use case, this will not scale. Instead, reshape the exception in a more business-oriented one.

The code can be improved by looking for more cases. For instance, the GatewayValidationException assumes all status code are validations (4xx, and 5xx), this is a simplification for the external gateway used, but is more correctly if it return 400 (bad request) for errors that are validations and the user can retry and 503 (internal server error) for the ones that the user can't do anything.

If we don't know what is going on, we rethorw a GatewayException with the cause

Note the use of "{}" and codes to the exceptions. This is where internationalization comes in. Messages.properties file

exception.unexpected=Unexpected Exception, cause: {0}
com.kugelbit.payment.core.business.payment.customer.notPresent=Could not find customer
com.kugelbit.payment.core.business.payment.customer.noChargeEmail=The customer has no charge email, make sure to associate one and try again
com.kugelbit.payment.core.business.payment.customer.paymentServiceException=Exception on processing payment cause: {0} 
com.kugelbit.payment.core.business.payment.transaction.noCustomer=Transaction needs a customer
com.kugelbit.payment.core.business.payment.transaction.noPaymentMethod=Transaction needs a payment method
com.kugelbit.payment.core.gateway.exception.cantCharge=Problem with the payment method while creating charge request
exception.gateway.notAccepted=The payment gateway {0} can't accept the payment. Error message {1}
exception.gateway.cantCancel=The payment gateway {0} can't cancel the payment. Error message {1}
exception.gateway.validation=Payment validation exception: {0}

Note: messages.properties without a convention can become a hassle for your team, it's a source of duplication and code conflicts. The example above is NOT good, Maybe I going to talk about better conventions in another post.

The exception handler

    @ControllerAdvice
    class RestExceptionHandler @Autowired
    constructor(private val messageSource: MessageSource, private val logger: Logger) {

        @ExceptionHandler(MethodArgumentNotValidException::class)
        internal fun handleArgumentNotValidException(ex: MethodArgumentNotValidException, locale: Locale): ResponseEntity<RestMessage> {

            val result = ex.bindingResult

            val errorMessages = result.allErrors
                    .map { objectError -> messageSource.getMessage(objectError, locale) }

            return ResponseEntity(RestMessage(errorMessages), HttpStatus.BAD_REQUEST)

        }

        @ExceptionHandler(Exception::class)
        fun handleExceptions(ex: Exception, locale: Locale): ResponseEntity<RestMessage> {

            val unexpectedError = "exception.unexpected"
            val errorMessage = messageSource.getMessage(unexpectedError, arrayOf(ex.message), locale)
            logger.error(errorMessage, ex)
            return ResponseEntity(RestMessage(errorMessage), HttpStatus.INTERNAL_SERVER_ERROR)

        }

        @ExceptionHandler(PaymentCoreSystemException::class)
        fun handleSystemException(ex: PaymentCoreSystemException, locale: Locale): ResponseEntity<RestMessage> {

            val errorMessage = if (ex.message == null) "" else extractMessage(ex.message!!, locale, ex.messageParameters)
            logger.error(errorMessage, ex, ex.message)
            return ResponseEntity(RestMessage(errorMessage), HttpStatus.INTERNAL_SERVER_ERROR)
        }

        private fun extractMessage(message: String, locale: Locale, parameters: List<String>): String {
            return if (message.startsWith("{") && message.endsWith("}")) {
                messageSource.getMessage(message.substring(1, message.length - 1), parameters.toTypedArray(), locale)
            } else {
                message
            }
        }

    }

This code is problematic, but do the trick. It checks for a message starting with { and ending with } to translate the message and return a ResponseEntity that is a Internal server error

That is a generic treatment, as the code evolves, I may want to catch the specific exception and add information for the client.

We have a working exception handler. Now lets do in a more professional and clean way.

Improving the handler response

This is taken directly from a new professional project. I took the time to get ideas from other project and improve it with my owns. The team is satisfied with the result, we want to extract to library and clean the code further.

We create 3 classes and 1 enum:

  • ApiError
  • DomainBusinessException
  • PropertiesMessageProxy
  • ExceptionHandlerConfig
  • MessageConstants

The ExceptionHandlerConfig is where the magic happens. Let's start by it, next code is writen in Java language.

    @ControllerAdvice
    public class ExceptionHandlerConfig extends ResponseEntityExceptionHandler {

      private final Logger logger;
      private final PropertiesMessageProxy messageProxy;

      public ExceptionHandlerConfig(Logger logger, PropertiesMessageProxy messageProxy) {
        this.logger = logger;
        this.messageProxy = messageProxy;
      }

      /**
       * Validates all Runtime exceptions, transforming to ApiError with generic message
       *
       * @param e Exception
       * @return ApiError for Json consumption
       */
      @ResponseStatus(INTERNAL_SERVER_ERROR)
      @ExceptionHandler(Exception.class)
      @ResponseBody
      public ApiError handleAll(Exception e) {
        logger.error(e.getMessage(), e);
        return ApiError.fromMessage(INTERNAL_SERVER_ERROR,
            messageProxy.messageFromKey(MessageConstants.APPLICATION_EXCEPTION_GENERIC));
      }

      @ResponseStatus(UNPROCESSABLE_ENTITY)
      @ExceptionHandler(DomainBusinessException.class)
      @ResponseBody
      public ApiError handleBusinessException(DomainBusinessException e) {
        if (!e.getMessageConstants().isPresent()) {
          return ApiError.fromException(UNPROCESSABLE_ENTITY, e);
        } else {
          return ApiError.fromMessage(UNPROCESSABLE_ENTITY,
              messageProxy.messageFromKey(e.getMessageConstants().get()));
        }
      }

      /**
       * Handle @Valid exceptions in methods with @RequestBody
       *
       * @param ex MethodException
       * @param headers Default HttpHeaders
       * @param status Status of the request
       * @param request The web request
       * @return ApiError for Json consumption
       */
      @Override
      protected ResponseEntity<Object> handleMethodArgumentNotValid(
          MethodArgumentNotValidException ex,
          HttpHeaders headers,
          HttpStatus status,
          WebRequest request) {

        return ResponseEntity.badRequest().body(ApiError.fromMessage(HttpStatus.BAD_REQUEST,
            messageProxy.getMessageFromListErrors(ex.getBindingResult().getAllErrors())));
      }

      /**
       * @return ApiError for Json consumption
       */
      @ExceptionHandler({ConstraintViolationException.class})
      public ResponseEntity<ApiError> handleConstraintViolation(
          ConstraintViolationException ex) {
        List<String> errors = new ArrayList<>();
        for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
          errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
        }

        ApiError apiError =
            ApiError.fromMessage(HttpStatus.BAD_REQUEST, errors);
        return ResponseEntity.badRequest().body(apiError);
      }

We are handling just 4 cases:

  • Generic exceptions
  • DomainBusinessExceptions (Base class for all business related exceptions)
  • Spring validations with @Valid annotation
  • ConstraintViolationExceptions for hibernate validations (there is a overlap with MethodArgumentNotValid)

In all cases we do return an ApiError which is

    @Data // lombok annotation
    class ApiError implements Serializable {

      private Integer statusCode;
      private String error;
      private List<String> messages;

      private ApiError(Integer statusCode, String error, String message) {
        this(statusCode, error,
            Objects.nonNull(message) ? Collections.singletonList(message) : Collections.emptyList());
      }

      private ApiError(Integer statusCode, String error, List<String> messages) {
        this.statusCode = statusCode;
        this.error = error;
        this.messages = messages;
      }

      public ApiError() {
        this.messages = Collections.emptyList();
      }

      public static ApiError fromException(HttpStatus httpStatus, Exception exception) {
        return fromException(httpStatus, exception, httpStatus.getReasonPhrase());
      }

      public static ApiError fromException(HttpStatus httpStatus, Exception exception,
          String errorIdentifier) {
        return new ApiError(httpStatus.value(), errorIdentifier, exception.getMessage());
      }

      public static ApiError fromMessage(HttpStatus httpStatus, String message) {
        return new ApiError(httpStatus.value(), httpStatus.getReasonPhrase(), message);
      }

      public static ApiError fromMessage(HttpStatus httpStatus, List<String> message) {
        return new ApiError(httpStatus.value(), httpStatus.getReasonPhrase(), message);
      }

Non spring, or CDI projects.

The same technique can be used on CDI. Check the project Apache DeltaSpike

Conclusion

I hope this post help you in build more robust, cleaner, and easier code.

The same thing can be delivered on other platforms, just check if you framework can help, it usually do.

For the frontend is a little different. I plan to add a blog post about how to handle this on Angular projects. The idea is the same.

The combination of REST, frontend and backend exception handling as the one we saw in this post, is powerful.

Is on my todo extract this in a plug and play library for spring and publish on maven central :-). I did that, but is closed sourced in some company repository.

 
Share this