DEV Community

Cover image for 🌟 Modern Global Exception Handling in Spring Boot
AYON KARMAKAR
AYON KARMAKAR

Posted on

🌟 Modern Global Exception Handling in Spring Boot

Exception handling is essential for building robust, user-friendly APIs. With Spring Boot, you can centralize error handling using a Global Exception Handler and custom exceptions for clean, maintainable code. This guide shows you how to set up a modern global exception handler, create a custom exception, and use them in a controller—all in a way that's easy to understand and quick to implement.


1. Create a Custom Exception: ResourceNotFoundException

A custom exception helps you signal specific error cases (like a missing resource) in your business logic.

// filepath: src/main/java/com/example/exception/ResourceNotFoundException.java
package com.example.exception;

/**
 * Thrown when a requested resource is not found.
 */
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Implement a Global Exception Handler

Centralize your error handling for all controllers. This example uses a modern response format and logs errors for debugging.

// filepath: src/main/java/com/example/exception/GlobalExceptionHandler.java
package com.example.exception;

import com.example.utils.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // Handle Resource Not Found exceptions
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiResponse<Object>> handleResourceNotFound(ResourceNotFoundException ex) {
        ApiResponse<Object> response = ApiResponse.error(ex.getMessage(), "RESOURCE_NOT_FOUND");
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    // Handle validation errors
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse<List<Map<String, String>>>> handleValidation(MethodArgumentNotValidException e) {
        List<Map<String, String>> details = e.getBindingResult()
            .getAllErrors()
            .stream()
            .map(error -> {
                Map<String, String> errorDetail = new HashMap<>();
                errorDetail.put("field", ((org.springframework.validation.FieldError) error).getField());
                errorDetail.put("message", error.getDefaultMessage());
                return errorDetail;
            })
            .collect(Collectors.toList());

        ApiResponse.Meta meta = new ApiResponse.Meta(
            Instant.now(),
            UUID.randomUUID().toString(),
            "1.0"
        );

        ApiResponse<List<Map<String, String>>> response = new ApiResponse<>(
            "fail",
            "Validation error!",
            details,
            meta,
            null
        );

        return new ResponseEntity<>(response, HttpStatus.UNPROCESSABLE_ENTITY);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Object>> handleGenericException(Exception exception) {
        log.error("Internal Server Error: {}", exception.getMessage(), exception);
        ApiResponse<Object> response = ApiResponse.error(exception.getMessage(), "INTERNAL_SERVER_ERROR");
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Example Controller Using the Exception

Here’s a simple controller method that demonstrates how the custom exception and global handler work together.

Note: Service logic is placed in the controller for demonstration.

Best Practice: Move business logic to a service class for maintainability.

// filepath: src/main/java/com/example/controller/CategoryController.java
package com.example.controller;

import com.example.entity.Category;
import com.example.exception.ResourceNotFoundException;
import com.example.utils.ApiResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/api/v1/categories")
public class CategoryController {

    // Example GET endpoint with inline logic for demonstration.
    // Best Practice: Move this logic to a service class!
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<Category>> getCategoryById(@PathVariable Long id) {
        List<Category> categories = Arrays.asList(
            new Category(1L, "Fruits", "Fresh fruits", true, "/images/fruits.png"),
            new Category(2L, "Vegetables", "Green veggies", false, "/images/veggies.png")
        );
        Category found = categories.stream()
            .filter(cat -> cat.getId().equals(id))
            .findFirst()
            .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + id));

        return ResponseEntity.ok(ApiResponse.success(found, "Category retrieved successfully"));
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Sample Error Response

When a category is not found, the API returns:

{
  "status": "fail",
  "message": "Validation error!",
  "data": [
    { "field": "name", "message": "Category name is required" }
  ],
  "meta": {
    "timestamp": "2025-09-17T12:34:56Z",
    "requestId": "b1a2c3d4-e5f6-7890-abcd-ef1234567890",
    "version": "1.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Best Practices

  • Centralize exception handling with @RestControllerAdvice.
  • Use custom exceptions for clear, domain-specific errors.
  • Keep business logic in service classes for clean controllers.
  • Return structured error responses for easy client integration.
  • Log errors for debugging and monitoring.

Conclusion

With a global exception handler and custom exceptions, your Spring Boot API will be easier to maintain, debug, and consume.

Start with the patterns above and adapt them to your project for a modern, professional backend!


If you found this guide helpful, share it with your team or star your repository!

Top comments (0)