Exception Handling in Spring Boot

Isaac Tonyloi - SWE - Jul 24 - - Dev Community

Exception handling is a critical part of building robust and user-friendly applications. In Spring Boot, we can handle exceptions in various ways to ensure our application remains stable and provides meaningful feedback to users. This guide will cover different strategies for exception handling, including custom exceptions, global exception handling, validation errors, and best practices for production.

1. Basics of Exception Handling

Exceptions are events that disrupt the normal flow of a program. They can be divided into:

  • Checked Exceptions: Exceptions that are checked at compile-time.
  • Unchecked Exceptions (Runtime Exceptions): Exceptions that occur during runtime.
  • Errors: Serious issues that applications should not handle, like OutOfMemoryError.

2. Custom Exception Classes

Creating custom exception classes helps in handling specific error conditions in your application.

package com.example.SpringBootRefresher.exception;

public class DepartmentNotFoundException extends RuntimeException {
    public DepartmentNotFoundException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Exception Handling in Controllers

@ExceptionHandler Annotation:
You can define methods to handle exceptions in your controller classes.

package com.example.SpringBootRefresher.controller;

import com.example.SpringBootRefresher.exception.DepartmentNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DepartmentController {

    @GetMapping("/department")
    public String getDepartment() {
        // Simulate an exception
        throw new DepartmentNotFoundException("Department not found!");
    }

    @ExceptionHandler(DepartmentNotFoundException.class)
    public ResponseEntity<String> handleDepartmentNotFoundException(DepartmentNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Global Exception Handling with @ControllerAdvice

To handle exceptions globally, you can use @ControllerAdvice and a centralized exception handler.

package com.example.SpringBootRefresher.error;

import com.example.SpringBootRefresher.entity.ErrorMessage;
import com.example.SpringBootRefresher.exception.DepartmentNotFoundException;
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.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
@ResponseStatus
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(DepartmentNotFoundException.class)
    public ResponseEntity<ErrorMessage> handleDepartmentNotFoundException(DepartmentNotFoundException exception, WebRequest request) {
        ErrorMessage message = new ErrorMessage(
                HttpStatus.NOT_FOUND.value(),
                exception.getMessage(),
                request.getDescription(false)
        );

        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(message);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorMessage> handleGlobalException(Exception exception, WebRequest request) {
        ErrorMessage message = new ErrorMessage(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                exception.getMessage(),
                request.getDescription(false)
        );

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Creating a Standard Error Response

Define a standard error response class to structure your error messages.

package com.example.SpringBootRefresher.entity;

public class ErrorMessage {
    private int statusCode;
    private String message;
    private String description;

    public ErrorMessage(int statusCode, String message, String description) {
        this.statusCode = statusCode;
        this.message = message;
        this.description = description;
    }

    // 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 getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Handling Validation Errors

Spring Boot integrates well with Bean Validation (JSR-380). To handle validation errors globally, use @ControllerAdvice.

package com.example.SpringBootRefresher.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
@ResponseStatus
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Using @ResponseStatus for Simple Exceptions

For simple cases, you can annotate an exception class with @ResponseStatus to specify the HTTP status code.

package com.example.SpringBootRefresher.exception;

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

@ResponseStatus(HttpStatus.NOT_FOUND)
public class DepartmentNotFoundException extends RuntimeException {
    public DepartmentNotFoundException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

8. Best Practices for Production

  1. Consistent Error Responses: Ensure your application returns consistent and structured error responses. Use a standard error response class.
  2. Logging: Log exceptions for debugging and monitoring purposes. Ensure sensitive information is not exposed in logs.
  3. User-Friendly Messages: Provide user-friendly error messages. Avoid exposing internal details or stack traces to users.
  4. Security: Be cautious about the information included in error responses to avoid exposing sensitive data.
  5. Documentation: Document your exception handling strategy for your team and future maintainers.

Summary

Exception handling in Spring Boot involves using annotations like @ExceptionHandler, @ControllerAdvice, and @ResponseStatus to manage errors effectively. By creating custom exceptions, handling validation errors, and following best practices, you can build robust applications that handle errors gracefully and provide meaningful feedback to users. Using Java 17 features ensures your application leverages the latest improvements in the Java ecosystem.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .