Labs ICT
โญ Pro Login

Exception Handling

Gracefully handling errors in your API.

Exception Handling

Let's be honest โ€” your code is going to have errors. Not because you're a bad developer, but because stuff happens. Networks fail, databases go down, users send weird data. Exception handling is how you turn those disasters into graceful responses.

Without proper exception handling, your app would just blow up and send back a nasty 500 error with a stack trace. Your users don't want to see that. Trust me.

@ControllerAdvice

Think of @ControllerAdvice as a safety net that catches exceptions across all your controllers. Instead of writing try-catch blocks in every single method, you handle everything in one place. It's like having one customer service desk instead of one per department.

You annotate a class with @ControllerAdvice and add methods that handle specific exception types. Spring Boot automatically routes exceptions to the right handler.

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        String message = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .collect(Collectors.joining(", "));
        ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", message);
        return ResponseEntity.badRequest().body(error);
    }
}

Custom Exceptions

Creating your own exception classes makes your code way more readable. Instead of throwing a generic RuntimeException with a cryptic message, you throw a ResourceNotFoundException or a DuplicateEmailException. It's self-documenting code.

Your custom exceptions should extend RuntimeException for unchecked exceptions, or Exception for checked ones. Most of the time, RuntimeException is what you want.

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String resource, String key, Object value) {
        super(String.format("%s not found with %s: '%s'", resource, key, value));
    }
}

public class DuplicateEmailException extends RuntimeException {
    public DuplicateEmailException(String email) {
        super("Email already registered: " + email);
    }
}

The beauty of this approach is that your controllers stay clean and focused on business logic. All the error handling lives in one place, making it easy to update your error responses without touching every controller method.

๐Ÿงช Quick Quiz

What is @ControllerAdvice used for?