Spring @RestControllerAdvice Annotation Example
Spring Framework, with its extensive support for building RESTful APIs, offers a convenient way to handle exceptions using the @RestControllerAdvice
annotation. This powerful annotation allows you to centralize and streamline your exception-handling logic across multiple controllers, making your code more maintainable and error-resistant.
In this blog, we will explore the @RestControllerAdvice
annotation and provide a working example to demonstrate its usage.
What is @RestControllerAdvice?
The @RestControllerAdvice
annotation in Spring is used to create a global exception handler that applies to all the @RestController
annotated classes within your application. It combines the functionality of the @ControllerAdvice
and @ResponseBody
annotations, making it perfect for building RESTful APIs.
By placing the @RestControllerAdvice
annotation on a class, you can define methods that handle exceptions thrown by any controller within your application. These methods can process the exceptions and return appropriate responses to the clients, such as error messages or custom error payloads in JSON format.
What we’ll build?
Suppose we have a simple Spring Boot application that provides a RESTful API for managing books in a library. We want to handle exceptions related to book operations uniformly across all our controllers.
What we’ll need?
- About 30 minute
- JDK 1.8 or later
- Spring Boot 2.3.4.RELEASE
- Gradle 4+ or Maven 3.2+
- Your favorite IDE:
- Spring Tool Suite (STS)
- Eclipse
- IntelliJ IDEA
Dependencies Required
Here is the build.gradle file including the required dependencies used in this project.
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'org.websparrow'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
Project Structure
The final project structure of the application in IntelliJ IDEA will look like as follows:
Let’s dive into a working example to understand the usage of @RestControllerAdvice
better step-by-step:
1. Define Exception Class
First, we need to create a custom exception class to represent the specific exception we want to handle. In our case, let’s create a BookNotFoundException
:
package org.websparrow.exception;
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(String message) {
super(message);
}
}
2. Create Global Exception Handler
Next, we’ll create an exception handler class annotated with @RestControllerAdvice
. This class will contain methods to handle different types of exceptions. Let’s name it GlobalExceptionHandler
:
package org.websparrow.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.websparrow.model.ErrorResponse;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleBookNotFoundException(BookNotFoundException ex) {
return new ErrorResponse("Book not found: " + ex.getMessage());
}
// Add more exception handling methods as needed
}
In the class, we define a method handleBookNotFoundException
annotated with @ExceptionHandler(BookNotFoundException.class)
. This method handles the BookNotFoundException
thrown by any controller. It returns an ErrorResponse
object, which will be converted to a JSON response automatically.
3. Define ErrorResponse Class
We need to create a simple class to represent the error response structure:
package org.websparrow.model;
import lombok.Data;
@Data
public class ErrorResponse {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
}
4. Define BookDTO and BookService Class
BookDTO
class will represent the book data model.
package org.websparrow.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class BookDTO {
private Long id;
private String title;
private String author;
}
And the BookService
class manage the call the database to find the book by id and return null
if no book found for the requested id.
package org.websparrow.service;
import org.springframework.stereotype.Service;
import org.websparrow.dto.BookDTO;
import java.util.List;
import java.util.Objects;
@Service
public class BookService {
// Simulate the actual database call here
public BookDTO getBook(Long id) {
return bookRepo().stream()
.filter(e -> Objects.equals(e.getId(), id))
.findFirst()
.orElse(null);
}
private List<BookDTO> bookRepo() {
return List.of(
new BookDTO(1L, "Ramayana", "Valmiki"),
new BookDTO(2L, "Bhagavad Gita", "Vyasa"),
new BookDTO(3L, "Head First Java", "Kathy Sierra"),
new BookDTO(4L, "Rich Dad Poor Dad", "Robert Kiyosaki")
);
}
}
Similar Posts:
5. Build the RESTful Controller
Now, let’s create a simple RESTful controller that performs book operations:
package org.websparrow.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.websparrow.dto.BookDTO;
import org.websparrow.exception.BookNotFoundException;
import org.websparrow.service.BookService;
@RestController
@RequestMapping("/books")
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping("/{id}")
public BookDTO getBook(@PathVariable("id") Long id) {
// Retrieve the book from the database
BookDTO book = bookService.getBook(id);
// If the book does not exist, throw BookNotFoundException
if (null == book) {
throw new BookNotFoundException("Book with ID " + id + " not found");
}
return book;
}
// Other controller methods
}
In the code, we intentionally throw a BookNotFoundException
when a book is not found by its ID.
6. Run the Application
Finally, start the Spring Boot application, and you’re ready to test the exception handling behavior. Send a GET request to /books/{id}
endpoint, where {id}
represents the ID of a non-existent book. You should receive a JSON response similar to the following:
{
"message": "Book not found: Book with ID 101 not found"
}
And the JSON response when a a book found:
{
"id": 1,
"title": "Ramayana",
"author": "Valmiki"
}
Summary
The @RestControllerAdvice
annotation in Spring provides a powerful and convenient way to handle exceptions in your RESTful APIs. By centralizing your exception handling logic, you can ensure consistent error responses and simplify your codebase. In this blog post, we explored the usage of @RestControllerAdvice
with a working example, showcasing its ability to handle exceptions uniformly across multiple controllers. Leveraging the capabilities of Spring Framework, you can enhance the reliability and user experience of your web applications.
References
- @RestControllerAdvice- SpringDoc
- Spring Boot + MongoDB CRUD Example
- Spring Boot RESTful CRUD Example with MySQL Database