DEV Community

Cover image for @RestControllerAdvice Annotation in Spring
bezkoder
bezkoder

Posted on

@RestControllerAdvice Annotation in Spring

In this tutorial, we're gonna look at an Spring Boot exception handling example that uses @RestControllerAdvice annotation. I also show you the comparison between @RestControllerAdvice and @ControllerAdvice along with the use of @ExceptionHandler annotation.

Full Article: https://bezkoder.com/spring-boot-restcontrolleradvice/

Rest API exception handling

We've created Rest Controller for CRUD Operations and finder method.
Let look at the code:
(step by step to build the Rest APIs is in:

@RestController
public class TutorialController {

  @Autowired
  TutorialRepository tutorialRepository;

  @GetMapping("/tutorials")
  public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
    try {
      ...
      return new ResponseEntity<>(tutorials, HttpStatus.OK);
    } catch (Exception e) {
      return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @GetMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  @PutMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
    Optional<Tutorial> tutorialData = tutorialRepository.findById(id);

    if (tutorialData.isPresent()) {
      ...
      return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }

  ...

  @DeleteMapping("/tutorials/{id}")
  public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
    try {
      tutorialRepository.deleteById(id);
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    } catch (Exception e) {
      return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @DeleteMapping("/tutorials")
  public ResponseEntity<HttpStatus> deleteAllTutorials() {
    // try and catch
  }

  @GetMapping("/tutorials/published")
  public ResponseEntity<List<Tutorial>> findByPublished() {
    // try and catch
  }
}
Enter fullscreen mode Exit fullscreen mode

You can see that we use try/catch many times for similar exception (INTERNAL_SERVER_ERROR), and there are also many cases that return NOT_FOUND.

Is there any way to keep them simple, any way to attach the error response message smartly and flexibility?
Let's solve the problem now.

@RestControllerAdvice annotation

Spring supports exception handling by a global Exception Handler (@ExceptionHandler) with Controller Advice (@RestControllerAdvice).

@RestControllerAdvice
public class ControllerExceptionHandler {

  @ExceptionHandler(value = {ResourceNotFoundException.class, CertainException.class})
  @ResponseStatus(value = HttpStatus.NOT_FOUND)
  public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(
        status,
        date,
        ex.getMessage(),
        description);

    return message;
  }
}
Enter fullscreen mode Exit fullscreen mode

The @RestControllerAdvice annotation is specialization of @Component annotation so that it is auto-detected via classpath scanning. It is a kind of interceptor that surrounds the logic in our Controllers and allows us to apply some common logic to them.

Rest Controller Advice's methods (annotated with @ExceptionHandler) are shared globally across multiple @Controller components to capture exceptions and translate them to HTTP responses. The @ExceptionHandler annotation indicates which type of Exception we want to handle. The exception instance and the request will be injected via method arguments.

By using two annotations together, we can:

  • control the body of the response along with status code
  • handle several exceptions in the same method

How about @ResponseStatus?

@RestControllerAdvice annotation tells a controller that the object returned is automatically serialized into JSON and passed it to the HttpResponse object. You only need to return Java body object instead of ResponseEntity object. But the status could be always OK (200) although the data corresponds to exception signal (404 - Not Found for example). @ResponseStatus can help to set the HTTP status code for the response:

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
  // ...    
  return message;
}
Enter fullscreen mode Exit fullscreen mode

@RestControllerAdvice with @ResponseEntity

If you use @RestControllerAdvice without @ResponseBody and @ResponseStatus, you can return ResponseEntity object instead.

@RestControllerAdvice
public class ControllerExceptionHandler {

  @ExceptionHandler(ResourceNotFoundException.class)
  @ResponseStatus(value = HttpStatus.NOT_FOUND)
  public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorMessage message = new ErrorMessage(...);

    return message;
  }
}
Enter fullscreen mode Exit fullscreen mode

@ControllerAdvice vs @RestControllerAdvice

For more details and implementing the example, please visit:
https://bezkoder.com/spring-boot-restcontrolleradvice/

Further Reading

If you want to add Pagination to this Spring project, you can find the instruction at:
Spring Boot Pagination & Filter example | Spring JPA, Pageable

To sort/order by multiple fields:
Spring Data JPA Sort/Order by multiple Columns | Spring Boot

Or way to write Unit Test for the JPA Repository:
Spring Boot Unit Test for JPA Repositiory with @DataJpaTest

More Practice:

Top comments (0)