Backend Development 7 min read

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

.

<code>@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()));
    }
}
</code>

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.

<code>@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()));
    }
}
</code>
<code>@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()));
    }
}
</code>

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.

<code>@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()));
    }
}
</code>

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

.

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

login 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.