Spring Boot

Article 11: Exception Handling the Right Way in Spring Boot

By Utility Zone · 2026-01-27T18:29:36.852375

1. Introduction

Errors are inevitable.

What separates professional APIs from amateur ones is:

  • How errors are handled
  • How clearly errors are communicated to clients

Spring Boot provides powerful mechanisms to handle exceptions centrally and cleanly.


2. The Problem Without Proper Exception Handling

Without proper handling:

  • Stack traces leak to clients
  • Inconsistent error responses
  • Duplicate try-catch blocks everywhere

Example (❌ bad practice):

try {
    // business logic
} catch (Exception e) {
    return "Something went wrong";
}

This approach:

  • Hides real issues
  • Is hard to maintain
  • Produces poor APIs

3. Common Exceptions in REST APIs

Typical exceptions:

  • EntityNotFoundException
  • IllegalArgumentException
  • MethodArgumentNotValidException
  • NullPointerException (should be avoided)

We must map these to proper HTTP responses.


4. Handling Exceptions at Controller Level

4.1 Using @ExceptionHandler

@RestController
public class SampleController {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
        return ResponseEntity.badRequest().body(ex.getMessage());
    }
}

✔ Works
❌ Not scalable (controller-specific)


5. Global Exception Handling with @ControllerAdvice

5.1 What Is @ControllerAdvice?

It allows:

  • Centralized exception handling
  • Reusable error responses
  • Clean controllers

5.2 Creating a Global Exception Handler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<String> handleNotFound(EntityNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneric(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Internal server error");
    }
}

6. Handling Validation Errors Properly

Spring throws:

MethodArgumentNotValidException

Handle it globally:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
        MethodArgumentNotValidException ex) {

    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(error ->
        errors.put(error.getField(), error.getDefaultMessage())
    );

    return ResponseEntity.badRequest().body(errors);
}

Produces clean, structured errors.


7. Creating Custom Exceptions

7.1 Example: ResourceNotFoundException

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

Throw it from service layer:

throw new ResourceNotFoundException("User not found");

8. Standard Error Response Structure

Professional APIs return consistent errors:

{
  "timestamp": "2026-01-01T10:15:30",
  "status": 404,
  "error": "Not Found",
  "message": "User not found",
  "path": "/users/10"
}

Consistency matters for frontend & clients.


9. Best Practices

✔ Handle exceptions globally
✔ Don’t expose stack traces
✔ Use meaningful HTTP status codes
✔ Throw exceptions from service layer
✔ Keep controllers clean


10. Common Beginner Mistakes

❌ Catching Exception everywhere
❌ Returning 200 OK for errors
❌ Exposing internal messages
❌ Mixing validation & business errors


11. What You Should Understand Before Moving On

You should now know:

  • Why global exception handling is important
  • How @ControllerAdvice works
  • How to handle validation & business exceptions
  • How to return clean error responses

12. What’s Next?

➡ Article 12: API Documentation with Swagger / OpenAPI

  • Swagger UI
  • API contracts
  • Developer-friendly APIs

Type Next when you’re ready.