nirmalakumarsahu

Spring Boot Exception Handling

πŸ“„ Articles πŸ‘€ My Profile

Spring Boot Exception Handling

Spring Framework Framework


πŸ“‘ Index


πŸ“Œ Introduction

❓ What is an Exception?

An exception is an unexpected event ⚑ that occurs during program execution and disrupts the normal flow of instructions.

πŸ‘‰ Examples in Java:

➑️ In short: exceptions are runtime errors caused by mistakes, invalid inputs, or system failures.

πŸ›‘οΈ What is Exception Handling?

Exception Handling = the process of responding to exceptions in a controlled way βœ… instead of letting the program crash πŸ’₯.

Benefits:

βš™οΈ Exception Handling in Java

Java provides a robust mechanism with: πŸ‘‰ try πŸ§ͺ catch 🎯 finally πŸ”’ throw 🎲 throws πŸ“’

πŸ’» Example:

public class Example {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // ⚑ ArithmeticException
        } catch (ArithmeticException ex) {
            System.out.println("❌ Cannot divide by zero!");
        } finally {
            System.out.println("βœ… This block always executes.");
        }
    }
}

πŸ“€ Output:

❌ Cannot divide by zero!
βœ… This block always executes.

Here:

βœ… So, in short:

πŸ” Back to Top


🌱 Exception Handling in Spring Boot

In Spring Boot, exception handling = managing errors & unexpected conditions in a way that:

πŸ‘‰ By default, Spring Boot shows a whitelabel error page or JSON error response. In real-world apps, we customize it.

πŸ“ Spring Boot Exception Handling Internal Flow

πŸ’‘ Let’s understand how Spring Boot Exception Handling works.

spring-boot-exception-handling-internal-flow.png

πŸ” Back to Top


πŸ› οΈ Different Ways to Handle Exceptions in Spring Boot

1️⃣ Using try-catch (Local Handling)

The simplest way: wrap risky code inside try-catch in service/controller.

@GetMapping("/product/{id}")
public ResponseEntity<?> getProduct(@PathVariable Long id) {
    try {
        Product product = productService.findById(id);
        return ResponseEntity.ok(product);
    } catch (ProductNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("❌ Product not found");
    }
}

βœ… Simple but ❌ clutters controllers β†’ not recommended for big apps.

2️⃣ Using @ExceptionHandler (Controller Level)

You can handle exceptions inside a controller using @ExceptionHandler.

@RestController
@RequestMapping("/products")
public class ProductController {

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.findById(id);
    }

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<String> handleProductNotFound(ProductNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("❌ " + ex.getMessage());
    }
}

βœ… Keeps controller clean, but exception handling is limited to that controller.

3️⃣ Using @ControllerAdvice / @RestControllerAdvice (Global Handling 🌍 / βœ… Best Practice)

This is the industry standard approach for global exception handling.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ApiError> handleProductNotFound(ProductNotFoundException ex) {
        ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage(), LocalDateTime.now());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class) // fallback
    public ResponseEntity<ApiError> handleGeneral(Exception ex) {
        ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "⚠️ Something went wrong!", LocalDateTime.now());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

πŸ“ DTO for response:

@Data
@AllArgsConstructor
public class ApiError {
    private HttpStatus status;
    private String message;
    private LocalDateTime timestamp;
}

βœ… Centralized error handling, reusable & clean 🎯

4️⃣ Using ResponseStatusException

You can throw exceptions with HTTP status codes directly.

@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
    return productService.findById(id)
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "❌ Product not found"));
}

βœ… Quick, useful for simple APIs. ❌ Not very flexible for structured responses.

5️⃣ Using @ResponseStatus on Custom Exceptions

Annotate custom exceptions with HTTP status.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ProductNotFoundException extends RuntimeException {
    public ProductNotFoundException(String message) {
        super(message);
    }
}

When thrown, Spring automatically returns 404 Not Found. βœ… Clean, no extra handler needed. ❌ Less control over response body.

6️⃣ Extending ResponseEntityExceptionHandler (Spring Built-in)

Spring provides ResponseEntityExceptionHandler for handling common exceptions like validation, MethodArgumentNotValidException, etc.

@RestControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, 
            HttpStatus status, WebRequest request) {
        
        List<String> errors = ex.getBindingResult().getFieldErrors()
                .stream().map(err -> err.getField() + ": " + err.getDefaultMessage())
                .toList();

        ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, "Validation failed", errors);
        return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST);
    }
}

βœ… Best for handling Spring validation exceptions.

πŸ† Industry Best Practice

βœ”οΈ Use @RestControllerAdvice with custom exceptions 🎯 (clean, centralized, standard).

βœ”οΈ Combine with ResponseEntityExceptionHandler for validation πŸš€

βœ”οΈ Use @ResponseStatus for simple cases ⚑

πŸ” Back to Top


πŸš€ Implementation

πŸ—οΈ Technology Stack

πŸ–₯️ Backend

πŸ“Š API Documentation

πŸ›’οΈ Database

πŸ› οΈ Build & Dependency Management

βš™οΈ Utilities

πŸ“‚ Project Structure

spring-boot-exception-handling
│── πŸ“‚ src/
β”‚   └── πŸ“‚ main/
β”‚       β”œβ”€β”€ πŸ“‚ java/
β”‚       β”‚   └── πŸ“‚ com/sahu/springboot/basics/
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ config/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ OpenApiConfig.java
β”‚       β”‚       β”‚   └── πŸ“„ OpenApiProperties.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ constant/
β”‚       β”‚       β”‚   └── πŸ“„ ApiStatus.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ controller/
β”‚       β”‚       β”‚   └── πŸ“‚ rest/
β”‚       β”‚       β”‚       └── πŸ“„ ProductRestController.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ dto/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ApiResponse.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductRequest.java
β”‚       β”‚       β”‚   └── πŸ“„ ProductResponse.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ exception/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ GlobalExceptionHandler.java
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“„ ProductAlreadyExistException.java
β”‚       β”‚       β”‚   └── πŸ“„ ProductNotFoundException.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ model/
β”‚       β”‚       β”‚   └── πŸ“„ Product.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ repository/
β”‚       β”‚       β”‚   └── πŸ“„ ProductRepository.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ service/
β”‚       β”‚       β”‚   β”œβ”€β”€ πŸ“‚ impl/
β”‚       β”‚       β”‚   β”‚   └── πŸ“„ ProductServiceImpl.java
β”‚       β”‚       β”‚   β”‚
β”‚       β”‚       β”‚   └── πŸ“„ ProductService.java
β”‚       β”‚       β”‚
β”‚       β”‚       β”œβ”€β”€ πŸ“‚ util/
β”‚       β”‚       β”‚   └── πŸ“„ ProductUtil.java
β”‚       β”‚       β”‚
β”‚       β”‚       └── πŸ“„ SpringBootExceptionHandlingApplication.java
β”‚       β”‚
β”‚       └── πŸ“‚ resources/
β”‚           └── πŸ“„ application.yml
β”‚
β”œβ”€β”€ πŸ“„ docker-compose.yml
└── πŸ“„ pom.xml

πŸ”— Code Repository

You can find the complete code repository for this project on GitHub:

GitHub - spring-boot-exception-handling

πŸš€ To Run the Spring Boot Application

1️⃣ 🐳 Using Docker Compose (for MySQL container)

docker-compose up -d

βœ… This starts MySQL in a container (-d = detached mode). πŸ” Verify with:

docker ps

πŸ“Œ DB is now available at localhost:3307. πŸ”‘ Credentials (username, password, DB name) are in docker-compose.yml.

2️⃣ πŸ’» Run Directly in IntelliJ IDEA

  1. πŸ“‚ Open the Spring Boot project in IntelliJ.
  2. ▢️ In Project Explorer, right-click the main class: SpringBootExceptionHandlingApplication.java
  3. Select Run β€˜SpringBootExceptionHandlingApplication’.
  4. 🐞 For debugging, click the Debug button instead of Run.
  5. 🌐 App will start on http://localhost:9897.

3️⃣ ⚑ Run with Maven Command (CLI)

πŸ” Back to Top


πŸŽ₯ Video Reference

For a detailed running and demonstration of the application walkthrough,
watch the following YouTube video:

Watch the video

πŸ” Back to Top

πŸ“– Read More ➑️