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.

build.gradle
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:

Spring @RestControllerAdvice Annotation Example

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:

BookNotFoundException.java
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:

GlobalExceptionHandler.java
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:

ErrorResponse.java
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.

BookDTO.java
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.

BookService.java
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:

BookController.java
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

  1. @RestControllerAdvice- SpringDoc
  2. Spring Boot + MongoDB CRUD Example
  3. Spring Boot RESTful CRUD Example with MySQL Database

Similar Posts

About the Author

Atul Rai
I love sharing my experiments and ideas with everyone by writing articles on the latest technological trends. Read all published posts by Atul Rai.