Blog

Effective Exception Handling in Spring Boot REST APIs

When building robust REST APIs in Spring Boot, exception handling plays a crucial role in providing meaningful responses to clients while ensuring security and maintainability. This blog delves into best practices for handling exceptions in Spring Boot applications, focusing on the use of @ControllerAdvice to centralize exception handling.


Why Handle Exceptions Gracefully?

  1. Security: Exposing stack traces to clients is a potential security risk, as it reveals internal details about the application.
  2. User Experience: Meaningful error messages help clients understand and resolve issues effectively.
  3. Maintainability: Centralized exception handling reduces code clutter in controllers and makes the application easier to maintain.

Typical Flow in a Spring Boot API

  1. Request to Controller: The controller reads parameters and delegates logic to the service layer.
  2. Service Layer: Executes business logic and may throw exceptions for invalid or missing data.
  3. Response Formation: The controller formats the data and sends it back to the client.
  4. Exception Handling: If an exception is thrown at any layer, it must be handled gracefully to return meaningful responses.

Challenges in Exception Handling

  • Unchecked Exceptions: Runtime exceptions (e.g., NullPointerException, ArithmeticException) might be missed unless explicitly handled.
  • Checked Exceptions: Compile-time exceptions must be handled or declared, ensuring a level of safety.
  • Messy Controller Code: Adding multiple try-catch blocks in controllers makes code less readable and maintainable.

Solution: @ControllerAdvice in Spring Boot

Spring Boot provides @ControllerAdvice to centralize and standardize exception handling across all controllers. It acts as middleware, intercepting exceptions and allowing custom responses.

Benefits of @ControllerAdvice

  1. Centralized exception handling for all controllers.
  2. Ability to modify responses (status codes, headers, body) before sending them to the client.
  3. Cleaner and more readable controller code.

Implementing Exception Handling with @ControllerAdvice

1. Define Custom Exceptions

Custom exceptions make error handling more descriptive and specific.

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

2. Create a Global Exception Handler

The @ControllerAdvice class handles exceptions thrown by controllers and services.

Java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(ArithmeticException.class)
    public ResponseEntity<String> handleArithmeticException(ArithmeticException ex) {
        return new ResponseEntity<>("Invalid arithmetic operation: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3. Modify Controller Code

Controllers delegate exception handling to @ControllerAdvice, resulting in cleaner code.

Java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/products/{id}")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        Product product = productService.getProductById(id);
        return new ResponseEntity<>(product, HttpStatus.OK);
    }
}

4. Service Layer Logic

The service layer throws exceptions for invalid scenarios, leaving handling to the controller or @ControllerAdvice.

Java
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    public Product getProductById(Long id) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("Invalid product ID");
        }

        // Simulate a product not found scenario
        if (id == 999) {
            throw new ProductNotFoundException("Product with ID " + id + " does not exist");
        }

        // Simulate a valid product
        return new Product(id, "Sample Product", 99.99);
    }
}

5. Example Output

  • Valid Request: GET /products/1 HTTP/1.1 200 OK { "id": 1, "name": "Sample Product", "price": 99.99 }
  • Product Not Found: GET /products/999 HTTP/1.1 404 NOT FOUND Product with ID 999 does not exist
  • Invalid Arithmetic Operation: GET /products/divide-by-zero HTTP/1.1 400 BAD REQUEST Invalid arithmetic operation: / by zero

Types of @ControllerAdvice

  1. Exception Handling: Handle specific exceptions using @ExceptionHandler methods.
  2. Model Enhancements: Modify or enrich model attributes globally.
  3. Binder Initialization: Customize data binding and validation logic.
  4. Response Modifications: Adjust headers, status codes, or the response body before sending to clients.

Conclusion

Using @ControllerAdvice, you can centralize and streamline exception handling in Spring Boot applications. This approach ensures:

  • Enhanced security by hiding technical stack traces.
  • Improved client experience with meaningful responses.
  • Cleaner and maintainable code in controllers and services.

By following these practices, your APIs will be robust, secure, and user-friendly.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over