Search code examples
javarestkotlinretrofit2response-entity

GlobalExceptionHandler with @ExceptionHandler or ResponseEntity for REST answer from the server?


I've found two approaches of returning error status codes from RESTful API: the first is traditional using ResponseEntity<>. The second one is to create specific exceptions for status codes and using GlobalExceptionHandler with @ExceptionHandler throw these exceptions as answer of the server. Spring will catch it and send as answer.

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ResponseEntity<AppError> catchResourceNotFoundException(ResourceNotFoundException e) {
        log.error(e.getMessage(), e);
        return new ResponseEntity<>(new AppError(HttpStatus.NOT_FOUND.value(), e.getMessage()), HttpStatus.NOT_FOUND);
    }
 }

I don't know what is better. For second approach Controller will look like this:

@GetMapping("/products")
    public Product getProduct(Long id){
            return productsService.findById(id);
    }

And service as:

public Product findById(Long id) {
        return productsRepository.findById(id).orElseThrow(
                () -> new ResourceNotFoundException("Product with id " + id + " not found"));
    }
  1. What is the best approach and why?

  2. Is it hard to get ResponseEntity on Kotlin android retrofit api if first approach better? What will I get if server sends to my Kotlin side ResponseEntity? Response class? Server on Java Spring, client - on Android Kotlin

Someone says that ResponseEntity is the best approach to send data from the server. Someone says that it's difficult to read controllers with ResponseEntity in return value like this:

@GetMapping("/products")
    public ResponseEntity<?> getProduct(Long id){
        try {
            Product product = productsService.findById(id).orElseThrow();
            return new ResponseEntity<>(product, HttpStatus.OK);
        } catch (Exception e){
            return new ResponseEntity<>(new AppError(HttpStatus.NOT_FOUND.value(), 
                    "Product with id " + id + " nor found"),
                    HttpStatus.NOT_FOUND);
        }
    }

So where is the truth?


Solution

  • It is always better to let exception handling for Spring. Just by showing these 2 approaches you can see that you will write less code and your "business" code will be much cleaner without these try catches. (Of course, if you want to use that approach for some reason on some places, you are free to go).

    With first approach you can easily make your rest api more convenient and readable for users who will consume it.

    For instance, you can create a global exception handler and set of exception children which you can easily map to correct error code and possibly error message as well.

    @ResponseStatus(NOT_FOUND)
    @ExceptionHandler(NotFoundExceptionParent.class)
    @ResponseBody
    public ResponseEntity<GenericErrorObject> notFoundExceptionHandling(NotFoundExceptionParentex) {
        // Get message locale.id from given exception in language that user uses (you can store arguments in exception so you can easily fill arguments to that message, for example saying "Entity with id {} was not found"
        // Create generic object that holds that error message
        GenericErrorObject genericError = createGenericObject(ex);
    
        return ResponseEntity.status(NOT_FOUND_STATUS).body(genericError );
    }
    

    So every exception that inherits from NotFoundExceptionParent will be handled here (so ends with 404 REST response every time). And will result in some generic error object like

    {
     error: {
       messageIdentifier: "some.message.identifier"
       messageContent: "Item with id 5 was not found"
       incidentUUID: "some-uuid"
       // possibly more data
     }
    }
    

    that your consumer can, generically, handle as well. If we can take, for instance, some simple FE app (Vue, Angular, React, whatever), you can easily implement some default handling (which you can override on some places of course) that will, on non 2XX response, in some generic error toast where is just print error.messageContent (and voila, you have your basic error handling for "almost" free :))

    Keep in mind that you should have some "fallback" error handling there too, catching for example RuntimeException (if possible) that will end in some 500 or something. Also do not forget to add some additional logging, global exception handler is perfect place to log errors and possibly generate some unique identifier for them so you can track these errors easily in logs.

    If you want to perform completely different handling for some validations (and you dont want to mix it together), you can do so by implementing another exception handler with different exception parent.

    And the only thing you have to do to perform this error handling would be throw some custom exception from any place. That sounds much better than app swarming with try catches, isn´t it.

    Final note: Handling error the "first way" is not necessarily bad approach, if your use cases does not really fit to some generic exception handling system, feel free to use custom handling. But if Spring can do anything for you, why not let it to do it :)