Spring Boot Tutorial

Spring Boot - Software Setup and Configuration (STS/Eclipse/IntelliJ)

Prerequisite (Spring Core Concepts)

Spring Boot Core

Spring Boot with REST API

Spring Boot with Database and Data JPA

Spring Boot with Kafka

Spring Boot with AOP

Spring Boot - Exception Handling

In Spring Boot applications, handling exceptions properly is crucial to ensure a good user experience and maintainability of the code. Spring Boot provides several ways to handle exceptions effectively:

  1. @ControllerAdvice and @ExceptionHandler:

    • @ControllerAdvice: It's a centralized way of handling exceptions for your controllers. You can define a class with this annotation, and within that class, you can define methods annotated with @ExceptionHandler.
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(value = {SomeSpecificException.class})
        public ResponseEntity<Object> handleSomeSpecificException(SomeSpecificException ex) {
            // logic to handle the exception
            return new ResponseEntity<>("Error message or object", HttpStatus.BAD_REQUEST);
        }
    
    }
    
    • @ExceptionHandler: You can use this inside controllers to handle exceptions specific to that controller.
    @RestController
    public class MyController {
        
        @ExceptionHandler(value = {AnotherException.class})
        public ResponseEntity<Object> handleAnotherException(AnotherException ex) {
            // logic to handle the exception
            return new ResponseEntity<>("Error message for this controller", HttpStatus.BAD_REQUEST);
        }
    
        // ... your controller methods ...
    
    }
    
  2. Custom Error Attributes:

    You can customize the error attributes returned during an exception by implementing the ErrorAttributes interface.

  3. Custom ErrorController:

    By default, Spring Boot provides a /error mapping through its BasicErrorController. If you wish to customize this behavior, you can implement the ErrorController interface.

  4. ResponseStatusException:

    Spring 5 introduced ResponseStatusException, which can be thrown directly from controllers or service methods. It combines an HTTP status code and a reason string (or another exception).

    @GetMapping("/someEndpoint")
    public SomeObject getSomeObject() {
        if (someCondition) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Object not found");
        }
    
        // ... other logic ...
    }
    
  5. Error Responses:

    When designing your error responses, it's a good practice to create a standardized error response structure, so clients of your API always receive errors in a consistent format.

    public class ApiError {
        private LocalDateTime timestamp;
        private int status;
        private String error;
        private String message;
        private String path;
    
        // getters, setters, constructors ...
    }
    
  6. Using @Valid for Validations:

    For request body validations (for example, when you bind JSON to an object), you can use @Valid with your request model. If validation fails, a MethodArgumentNotValidException is thrown.

    @PostMapping("/create")
    public ResponseEntity<Object> createObject(@Valid @RequestBody SomeRequestModel request) {
        // ... method logic ...
    }
    

    You can then handle MethodArgumentNotValidException in your @ControllerAdvice class to return a meaningful error response.

  7. Logging:

    Ensure you log exceptions properly using a logging framework like SLF4J or Logback. This ensures that while the user gets a friendly and standardized error message, developers can have detailed logs to debug issues.

Exception handling in Spring Boot is powerful and flexible, allowing you to cater to a variety of requirements while ensuring that the application gracefully handles unexpected situations.

  1. Global exception handling in Spring Boot:

    • Implement a global exception handler to manage unhandled exceptions.
    • Use @ControllerAdvice and @ExceptionHandler.

    Example (GlobalExceptionHandler class):

    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(Exception.class)
        public ResponseEntity<String> handleException(Exception ex) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                 .body("An unexpected error occurred");
        }
    }
    
  2. Using @ExceptionHandler in Spring Boot:

    • Annotate methods with @ExceptionHandler to handle specific exceptions.
    • Provide customized handling logic for each exception.

    Example (UserController class):

    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
    
        @ExceptionHandler(UserNotFoundException.class)
        public ResponseEntity<String> handleUserNotFoundException(UserNotFoundException ex) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                                 .body("User not found: " + ex.getMessage());
        }
    }
    
  3. Custom exception handling in Spring Boot:

    • Create custom exception classes that extend RuntimeException.
    • Implement specific handling for these exceptions.

    Example (UserNotFoundException class):

    public class UserNotFoundException extends RuntimeException {
    
        public UserNotFoundException(String message) {
            super(message);
        }
    }
    
  4. Exception translation in Spring Boot:

    • Translate exceptions from lower layers to more meaningful ones.
    • Use @RepositoryRestResource(exported = false) to hide repository methods.

    Example (CustomDataAccessExceptionTranslator class):

    import org.springframework.dao.DataAccessException;
    import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
    
    public class CustomDataAccessExceptionTranslator extends PersistenceExceptionTranslationPostProcessor {
    
        @Override
        protected DataAccessException customTranslate(String task, String sql, DataAccessException ex) {
            return new CustomDataAccessException("Custom translation message", ex);
        }
    }
    
  5. Logging exceptions in Spring Boot applications:

    • Utilize logging frameworks like SLF4J or Logback.
    • Log exceptions at appropriate levels (e.g., ERROR).

    Example (MyService class with SLF4J):

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class MyService {
    
        private static final Logger logger = LoggerFactory.getLogger(MyService.class);
    
        public void process() {
            try {
                // Business logic that may throw an exception
            } catch (Exception e) {
                logger.error("An error occurred during processing", e);
            }
        }
    }
    
  6. Error response formats and HTTP status codes in Spring Boot:

    • Define consistent error response formats (e.g., JSON).
    • Use appropriate HTTP status codes for different scenarios.

    Example (ErrorResponse class):

    public class ErrorResponse {
    
        private int status;
        private String message;
    
        // Constructors, getters, and setters
    }
    
  7. Handling validation errors in Spring Boot:

    • Use @Valid annotation for request validation.
    • Handle validation errors with BindingResult in the controller.

    Example (UserController class):

    import org.springframework.validation.BindingResult;
    import org.springframework.validation.annotation.Validated;
    
    @RestController
    @Validated
    public class UserController {
    
        @PostMapping("/users")
        public ResponseEntity<String> createUser(@RequestBody @Valid User user, BindingResult result) {
            if (result.hasErrors()) {
                // Handle validation errors
            }
            // Continue with the normal flow
        }
    }
    
  8. Exception handling with ResponseEntity in Spring Boot:

    • Customize the response entity based on exception scenarios.
    • Use ResponseEntity to provide flexibility in the response.

    Example (UserController class):

    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    
    @RestController
    public class UserController {
    
        @ExceptionHandler(UserNotFoundException.class)
        public ResponseEntity<String> handleUserNotFoundException(UserNotFoundException ex) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                                 .body("User not found: " + ex.getMessage());
        }
    }