Master Global Exception Handling in Spring Boot with @ControllerAdvice

This article explains how to use Spring Boot's @ControllerAdvice and @ExceptionHandler annotations to implement clean, global exception handling, covering basic usage, controller‑specific handlers, and ordered advice for multiple handlers, with practical code examples and diagrams.

macrozheng
macrozheng
macrozheng
Master Global Exception Handling in Spring Boot with @ControllerAdvice

Preface

When developing APIs, uncaught exceptions often appear. Adding a simple try...catch at the outermost layer works but quickly becomes messy if applied to every controller method. Using Aspect‑Oriented Programming (AOP) with Spring Boot provides a cleaner solution.

Main Annotations

The two key annotations are @ControllerAdvice and @ExceptionHandler. @ControllerAdvice works as a global aspect for controllers. It can apply @ExceptionHandler, @InitBinder, and @ModelAttribute to all or selected controllers, depending on its attributes. @ExceptionHandler marks a method that handles a specific exception type. The value attribute defines the exception class, and it can be combined with @ResponseStatus to map to particular HTTP status codes.

Case Studies

Case 1 – Uniform Exception Structure

All APIs return the same error format. Create a GeneralExceptionHandler class annotated with @ControllerAdvice and define a method with @ExceptionHandler(Exception.class) that builds a custom MyError object and returns a ResponseEntity.

@ControllerAdvice
public class GeneralExceptionHandler {
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
        MyError myError = MyError.builder()
                .text(ex.getMessage())
                .code(ex.getErrorCode())
                .build();
        return new ResponseEntity(myError, HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Case 2 – Specific Exception for One Controller

For OtherController we can either add an @ExceptionHandler method inside the controller or create a dedicated @ControllerAdvice that targets only this controller.

@RestController
@RequestMapping("/other")
public class OtherController {
    @ExceptionHandler(OtherException.class)
    protected ResponseEntity<Error> handleException(OtherException ex) {
        MyOtherError myOtherError = MyOtherError.builder()
                .message(ex.getMessage())
                .origin("Other API")
                .code(ex.getErrorCode())
                .build();
        return new ResponseEntity(myOtherError, HttpStatus.valueOf(ex.getErrorCode()));
    }
}
@ControllerAdvice(assignableTypes = OtherController.class)
public class OtherExceptionHandler {
    @ExceptionHandler(OtherException.class)
    protected ResponseEntity<Error> handleException(OtherException ex) {
        MyOtherError myOtherError = MyOtherError.builder()
                .message(ex.getMessage())
                .origin("Other API")
                .code(ex.getErrorCode())
                .build();
        return new ResponseEntity(myOtherError, HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Case 3 – Multiple Advice with Ordering

When two advice classes may handle the same exception, use @Order to define precedence. Without explicit ordering, Spring may pick one arbitrarily, leading to unpredictable behavior across different launch modes.

@ControllerAdvice
public class GeneralExceptionHandler {
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
        MyError myError = MyError.builder()
                .text(ex.getMessage())
                .code(ex.getErrorCode())
                .build();
        return new ResponseEntity(myError, HttpStatus.valueOf(ex.getErrorCode()));
    }
}

@ControllerAdvice(assignableTypes = OtherController.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OtherExceptionHandler {
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Error> handleException(Exception ex) {
        MyError myError = MyError.builder()
                .message(ex.getMessage())
                .origin("Other API")
                .code(ex.getErrorCode())
                .build();
        return new ResponseEntity(myError, HttpStatus.valueOf(ex.getErrorCode()));
    }
}

Conclusion

These examples demonstrate how to implement most global exception‑handling scenarios in Spring Boot. Using @RestControllerAdvice can further simplify responses by automatically converting the returned object to JSON, eliminating the need to wrap it in ResponseEntity.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentException HandlingSpring BootControllerAdvice
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.