avatarVinotech

Summary

The provided content outlines a comprehensive approach to implementing global exception handling in a Spring Boot application using @ControllerAdvice and @ExceptionHandler annotations, along with custom exceptions and the @ResponseStatus annotation to standardize error responses.

Abstract

The text details a method for managing exceptions in Spring Boot applications by centralizing exception handling. It introduces the concept of global exception handling, which allows for consistent error responses and logging, as well as simplifying the error handling codebase. The article describes the use of @ControllerAdvice and @ExceptionHandler annotations to create a global exception handler that can intercept and process exceptions thrown by the application. It also covers the creation of custom exceptions, such as StudentNotFoundException, and how to use the @ResponseStatus annotation to define HTTP status codes for specific exceptions. The author provides a step-by-step example, including the creation of a Student class, a StudentService class, a GlobalExceptionHandler class, an ErrorDetails class, and a StudentController. The example demonstrates how to handle exceptions globally and return structured error responses to clients. The article concludes by emphasizing the benefits of using @ResponseStatus for straightforward use cases and the advantages of global exception handling for maintaining clean and consistent error handling logic.

Opinions

  • The author suggests that global exception handling is a powerful feature in Spring Boot that helps maintain clean and consistent error responses.
  • It is implied that handling exceptions separately in each component can lead to code duplication and inconsistency, which should be avoided.
  • The use of @ControllerAdvice and @ExceptionHandler is recommended for defining global exception handling across all controllers.
  • Custom exceptions, like StudentNotFoundException, are encouraged for specific error scenarios, providing clarity and ease of maintenance.
  • The @ResponseStatus annotation is presented as a simpler alternative for defining the HTTP status code and reason for an exception, suitable for less complex scenarios.
  • The author expresses that structured error responses, such as those provided by the ErrorDetails class, enhance the user experience and facilitate easier maintenance for developers.
  • The article promotes the sharing and dissemination of knowledge by encouraging readers to clap and share the content if they find it useful.

Global exception handling in spring boot

Imagine you have a Spring Boot application with multiple controllers and service methods. During the execution of these components, various exceptions can occur, such as database errors, validation failures, or unexpected runtime errors. Handling these exceptions separately in each component can lead to code duplication and inconsistency.

Global exception handling addresses this issue by allowing you to define a centralized exception handler that intercepts and manages all exceptions thrown by your application. This handler can perform tasks such as logging the error details, returning custom error responses to clients, or taking appropriate recovery actions.

Global exception handling in Spring Boot is a powerful feature that allows you to centralize the handling of exceptions thrown by your application. This can help you maintain clean and consistent error responses, log errors, and perform other necessary actions when exceptions occur.

Spring Boot provides several annotations and classes to achieve global exception handling. The most common approach is using the @ControllerAdvice annotation combined with @ExceptionHandler methods.

@ControllerAdvice and @ExceptionHandler

The @ControllerAdvice annotation allows you to define a class that will handle exceptions globally across all controllers. Within this class, you can use the @ExceptionHandler annotation to define methods that handle specific types of exceptions.

@ResponseStatus

You can also use the @ResponseStatus annotation to define the HTTP status code for a specific exception. This can be useful for custom exceptions that you define in your application.

Let’s create a complete example of a Spring Boot application with global exception handling, including a Student object. We'll define a REST controller for managing students, handle exceptions globally, and provide custom error responses.

Step 1: Create a Student Class

public class Student {
    private Long id;
    private String name;
    private Integer age;

    // Constructors, Getters, and Setters
    public Student(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Step 2: Create a StudentService Class

This service class will have methods to retrieve a student by ID. For demonstration purposes, it will throw a custom exception if the student is not found.

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class StudentService {
    private static Map<Long, Student> students = new HashMap<>();

    static {
        students.put(1L, new Student(1L, "John Doe", 20));
        students.put(2L, new Student(2L, "Jane Doe", 22));
    }

    public Student getStudentById(Long id) {
        if (!students.containsKey(id)) {
            throw new StudentNotFoundException("Student with ID " + id + " not found");
        }
        return students.get(id);
    }
}

Step 3: Create a Custom Exception

public class StudentNotFoundException extends RuntimeException {
    public StudentNotFoundException(String message) {
        super(message);
    }
}

Step 4: Create a Global Exception Handler

The global exception handler is responsible for catching exceptions thrown by any controller or service in the application and returning an appropriate response to the client.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(StudentNotFoundException.class)
    public ResponseEntity<?> handleStudentNotFoundException(StudentNotFoundException ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(HttpStatus.NOT_FOUND.value(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Step 5: Create an ErrorDetails Class

This class will be used to structure the error response.

public class ErrorDetails {
    private int statusCode;
    private String message;
    private String details;

    public ErrorDetails(int statusCode, String message, String details) {
        this.statusCode = statusCode;
        this.message = message;
        this.details = details;
    }

    // Getters and Setters
    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }
}

Step 6: Create a StudentController Class

This controller will expose a REST endpoint to get a student by ID.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/students/{id}")
    public Student getStudentById(@PathVariable Long id) {
        return studentService.getStudentById(id);
    }
}

Step 7: Testing the Global Exception Handling

When you run the application and try to retrieve a student by an ID that does not exist, the global exception handler will catch the StudentNotFoundException and return a 404 Not Found response with the error details.

Example Output

Request:

GET /students/3

Response:

{
    "statusCode": 404,
    "message": "Student with ID 3 not found",
    "details": "uri=/students/3"
}

If any other unexpected exception occurs, the global exception handler will catch it and return a 500 Internal Server Error response.

Summary

  • @ControllerAdvice: A specialization of the @Component annotation that allows defining global exception handling for controllers.
  • @ExceptionHandler: Used to define the method that will handle specific exceptions.

This setup allows you to handle exceptions consistently across your Spring Boot application, providing a better experience for users and easier maintenance for developers.

Using @ResponseStatus annotation You can use the @ResponseStatus annotation to directly set the HTTP status code and message for a specific exception. This approach is simpler and avoids the need for a global exception handler class, though it is less flexible for complex scenarios.

We will use the @ResponseStatus annotation to set the HTTP status code and reason for the StudentNotFoundException.

Create the Student Class

The Student class remains the same as before.

public class Student {
    private Long id;
    private String name;
    private Integer age;

    // Constructors, Getters, and Setters
    public Student(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Modify the Custom Exception with @ResponseStatus

We will use the @ResponseStatus annotation to set the HTTP status code and reason for the StudentNotFoundException.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Student not found")
public class StudentNotFoundException extends RuntimeException {
    public StudentNotFoundException(String message) {
        super(message);
    }
}
  • value: The HTTP status code to return (e.g., HttpStatus.NOT_FOUND).
  • reason: The reason phrase that will be returned to the client.

The StudentService class remains unchanged. It will throw the StudentNotFoundException if the student is not found.

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class StudentService {
    private static Map<Long, Student> students = new HashMap<>();

    static {
        students.put(1L, new Student(1L, "John Doe", 20));
        students.put(2L, new Student(2L, "Jane Doe", 22));
    }

    public Student getStudentById(Long id) {
        if (!students.containsKey(id)) {
            throw new StudentNotFoundException("Student with ID " + id + " not found");
        }
        return students.get(id);
    }
}

Update the StudentController Class

The StudentController class remains unchanged.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping("/students/{id}")
    public Student getStudentById(@PathVariable Long id) {
        return studentService.getStudentById(id);
    }
}

Testing the Application

When you run the application and try to retrieve a student by an ID that does not exist, the @ResponseStatus annotation will automatically set the HTTP status code and reason phrase.

Example Output

Request:

GET /students/3

Response:

{
    "timestamp": "2024-09-01T12:00:00.000+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "Student not found",
    "path": "/students/3"
}

Summary

  • @ResponseStatus: Used to mark a method or an exception class with the status code and reason that should be returned.
  • This approach is simple and works well for straightforward use cases where you don’t need complex error handling logic.

This method offers a quick and clear way to define the response status and message for specific exceptions in your Spring Boot application.

👏 If you found my articles useful, please consider giving it claps and sharing it with your friends and colleagues.

Global Exception Handling
Exception Handling
Spring Exception Handling
Custom Exception
Spring Boot
Recommended from ReadMedium