'Spring boot exception handling best practice

I have a simple question about exception handling. I currently have an application divided into multiple layers: controller, service, repository, and my question is the following: the exception handling should be done by the controller or the service?

Example:

Controller:

@PostMapping(value = "/{id}/parents", produces = "application/json; charset=utf-8")
public ResponseEntity<AccommodationRequestDTO> resident(@PathVariable Long id, @Valid @RequestBody ParentsAndUrgencyContactDTO parentsAndUrgencyContactDTO) {
    AccommodationRequestDTO saved;
    try {
        saved = this.service.parents(id, parentsAndUrgencyContactDTO);
    } catch (Exception e) {
        throw new ResponseStatusException(
                HttpStatus.INTERNAL_SERVER_ERROR,
                "Failed to save request", e);
    }
    return ResponseEntity.ok(saved);
}

Service:

public AccommodationRequestDTO parents(Long id, ParentsAndUrgencyContactDTO parentsAndUrgencyContactDTO) {
    Optional<AccommodationRequest> accommodationRequest = repository.findById(id);

    if (accommodationRequest.isPresent()) {
        AccommodationRequest saved = accommodationRequest.get();
        Parent firstParent = parentMapper.toEntity(parentsAndUrgencyContactDTO.getFirstParent());
        Parent secondParent = parentMapper.toEntity(parentsAndUrgencyContactDTO.getSecondParent());

        firstParent = parentRepository.save(firstParent);
        secondParent = parentRepository.save(secondParent);

        saved.setFirstParent(firstParent);
        saved.setSecondParent(secondParent);
        saved = this.repository.save(saved);

        return mapper.toDTO(saved);
    } else {
        throw new ResponseStatusException(
                HttpStatus.NOT_FOUND, " with id " + id + " not found !");
    }

}

What is the best practice, should I remove my try-catch from the controller and put it in my service? Because with this code my 404 Exception is overridden by the controller catch.



Solution 1:[1]

If you want to throw a exception throw it in service layer.

It will be better to define one more package named exception.

and have your custom exception, exception response and exception handler in it.

My code structure looks like:

package com.***.demo.exception;

public class DataNotFound extends EntityNotFoundException {
    public DataNotFound(String message) {
        super(message);
    }
}
package com.***.demo.exception;

@Data
@AllArgsConstructor
public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;
}
package com.***.demo.exception;

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(DataNotFound.class)
    public ResponseEntity<?> dataNotFoundExceptionHandling(Exception exception, WebRequest request) {
        return new ResponseEntity<>(new ErrorDetails(new Date(), exception.getMessage(), request.getDescription(false)), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globalExceptionHandling(Exception exception, WebRequest request) {
        return new ResponseEntity<>(new ErrorDetails(new Date(), exception.getMessage(), request.getDescription(false)), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Hope you got your answer.

Solution 2:[2]

Yes you need to move to service. You can handle exception by creation class that extends ResponseEntityExceptionHandler

Solution 3:[3]

if you don't use @ControllerAdvice, the best practice when the exception type decide what you want to present on client is throws something from your service and catch it specifically on controller. so, every method catch every specific exception and decide what the output depends on exception type. for example: when it's nullpointer the output is error 500, when it's missing request the output is error 400, etc.

unless the exception is something that you need to handle by your business logic, then it should be catch on your use case service.

But if you use Spring (or any frameworks which have Exception Handler) I prefer to use @ControllerAdvice as you don't need to catch every single things on controller anymore, just put throws Exception on every controller method and it will be catch on @ControllerAdvice class. so, any logic to decide the output is based on that class globally(or you can set it up to only handles from some classes only or some packages only) and your controller method will be shorter, cleaner and reduce code duplication.

Solution 4:[4]

Considering your spring boot application has a controller, service and repository layer.

You should always implement a ControllerAdvice class. This will ensure proper error handling for your clients.

The Service Layer should be able to catch and treat all Checked Exceptions. These exceptions are recoverable. A RuntimeException on the other hand, is a critical error and should stop the process.

Simple use-case: client requests information from service via http.

The repository accesses the database, but is confronted with an exception, because the db is not available at the moment. The Repo throws a RuntimeException.

This exception will be catched by the ControllerAdvice class. It will log the error and build an appropriate ResponseEntity for the client.

This is important, because the ResponseEntity should not contain any sensitive information concerning the architecture of your application.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 krishnkant jaiswal
Solution 2 Harini Krishnan
Solution 3 Ferry
Solution 4