skip to content

Reactive Exception Handling

@ExceptionHandler and @ControllerAdvice still work, and below them WebExceptionHandler plus ErrorAttributes shape the default reactive error response. Knowing both levels lets you answer where an error that escapes your controller ends up.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

How do you handle an exception thrown by a WebFlux controller method, both locally and globally?

level: juniorimportance: must knowfreq 70%

answer

  1. @ExceptionHandler = local; @ControllerAdvice = global
  2. @RestControllerAdvice adds @ResponseBody
  3. Handler can return Mono<ResponseEntity>
  4. Local wins over advice; most-specific type wins
  5. Only catches controller-method errors, not filter errors

basics

~10 s

Add a method annotated with @ExceptionHandler in the same controller to handle it locally. To handle exceptions from many controllers in one place, put @ExceptionHandler methods in a class annotated with @ControllerAdvice (or @RestControllerAdvice).

solid answer

~40 s

In WebFlux, @ExceptionHandler works much like in Spring MVC. A method annotated with @ExceptionHandler(SomeException.class) inside a @Controller/@RestController handles exceptions raised by that controller's request-mapping methods. To share handling across controllers, move those methods into a class annotated with @ControllerAdvice (or @RestControllerAdvice, which adds @ResponseBody so return values are serialized). Handler methods can return reactive types — Mono<ResponseEntity<...>>, Mono<ErrorBody>, etc. — and Spring resolves the return value exactly like a normal controller response. You can set the HTTP status with @ResponseStatus or by returning a ResponseEntity. Local controller handlers take precedence over @ControllerAdvice ones. This layer only catches exceptions that surface from controller invocation; errors from filters or outside the handler chain fall through to the global WebExceptionHandler chain.

code

java · 17 lines
java
@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public Mono<ResponseEntity<ApiError>> handleNotFound(ResourceNotFoundException ex,
                                                         ServerWebExchange exchange) {
        ApiError body = new ApiError("NOT_FOUND", ex.getMessage(),
                exchange.getRequest().getPath().value());
        return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).body(body));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST) // sets status; return value is the body
    public ApiError handleBadRequest(IllegalArgumentException ex) {
        return new ApiError("BAD_REQUEST", ex.getMessage(), null);
    }
}

go deeper

for a junior

Know the two annotations and that local beats global.

for a middle

Know handlers can return reactive types and that only controller-invocation errors are caught.

for a senior

Explain the boundary vs the WebExceptionHandler chain and functional endpoints.

for a principal

Discuss when to use annotation handlers vs a global ErrorWebExceptionHandler across mixed annotated/functional apps.

## What this covers WebFlux (Spring's reactive, non-blocking web stack running on Reactor `Mono`/`Flux`) offers the same annotation-based exception handling model as Spring MVC. ### @ExceptionHandler (controller-local) Inside a `@RestController` you can declare a method annotated with `@ExceptionHandler(MyException.class)`. When any `@RequestMapping`/`@GetMapping`/etc. method in that same controller throws (or returns a `Mono` that errors with) `MyException`, Spring invokes the handler method instead. The handler's return value is rendered exactly like a normal controller return value: - Return a `ResponseEntity<T>` (or `Mono<ResponseEntity<T>>`) to control status + body + headers. - Return a plain body object; set the status separately with `@ResponseStatus(HttpStatus.BAD_REQUEST)` on the handler method. - Return reactive types: `Mono<ErrorResponse>`, `Flux<...>` — these are supported because WebFlux resolves return values asynchronously. Handler methods can inject useful arguments: the exception itself, `ServerWebExchange`, `ServerHttpRequest`, `ServerHttpResponse`, `WebSession` (as `Mono`), etc. (Servlet types like `HttpServletRequest` are NOT available — this is reactive.) ### @ControllerAdvice / @RestControllerAdvice (global) Moving `@ExceptionHandler` methods into a `@ControllerAdvice`-annotated class makes them apply across all (or a filtered subset of) controllers. `@RestControllerAdvice` = `@ControllerAdvice` + `@ResponseBody`, so returned objects are serialized (JSON) rather than treated as view names. You can scope advice with `@ControllerAdvice(basePackages=...)`, `assignableTypes=...`, or `annotations=...`. ### Precedence 1. A matching `@ExceptionHandler` in the throwing controller wins over an equally-specific one in `@ControllerAdvice`. 2. Among candidates, Spring picks the most specific exception-type match (closest superclass), similar to catch-block resolution. ### Scope / boundary This annotation layer only handles exceptions that arise from **controller (annotated-handler) invocation** — it is implemented by `ExceptionHandlingWebHandler` delegating to `WebExceptionHandler`s, but the `@ExceptionHandler` resolution itself lives in the handler-adapter machinery. Exceptions thrown by a `WebFilter`, by the routing before a controller is selected, or in functional (`RouterFunction`) endpoints are NOT seen by `@ExceptionHandler`; those fall through to the global `WebExceptionHandler` chain (ending in Boot's `DefaultErrorWebExceptionHandler`). ### Gotchas - Returning `Mono.error(ex)` from a controller is equivalent to throwing — it still routes to `@ExceptionHandler`. - Don't block inside a handler (no `.block()`); return reactive types. - `@ExceptionHandler` catches errors on the controller method's returned publisher, but not errors that happen *while the response body is being written/streamed* after the status line is committed — at that point the response is already committed and cannot be changed.

  • Does @ExceptionHandler work for functional (RouterFunction) endpoints?
    No. @ExceptionHandler is bound to annotated controllers. Functional endpoints handle errors with onErrorResume in the handler function or fall through to the global WebExceptionHandler chain.
  • What's the difference between @ControllerAdvice and @RestControllerAdvice?
    @RestControllerAdvice is @ControllerAdvice plus @ResponseBody, so returned objects are serialized to the body (JSON) instead of being interpreted as a view/template name.

saying these in an interview costs you the question

  • Claiming @ExceptionHandler can inject HttpServletRequest (that's the blocking Servlet stack, not WebFlux)
  • Calling .block() inside a reactive exception handler
  • Thinking @ExceptionHandler catches WebFilter or router-level errors

context

open as a page

What is the ErrorAttributes abstraction in reactive Spring, and how does DefaultErrorAttributes participate in the error response?

level: middleimportance: should knowfreq 45%

basics

~20 s

ErrorAttributes is an interface that produces the map of fields (status, error, message, path, timestamp...) used to build the error response body. DefaultErrorAttributes is Boot's implementation; it also stores the exception so the error handler can read it. Extend it to customize the fields.

open as a page

What is the WebExceptionHandler interface and how does the global exception-handling chain work in WebFlux?

level: middleimportance: should knowfreq 55%

basics

~20 s

WebExceptionHandler is a functional interface with one method: Mono<Void> handle(ServerWebExchange exchange, Throwable ex). Beans of this type form an ordered chain that catches exceptions not handled inside controllers. The first one to complete the exchange wins.

open as a page

What does Spring Boot's DefaultErrorWebExceptionHandler do, and how would you customize the error response it produces?

level: seniorimportance: should knowfreq 50%

basics

~20 s

It is Spring Boot's global fallback error handler for WebFlux. It renders a JSON (or Whitelabel HTML) error response for any unhandled exception, using ErrorAttributes for the body. Customize it by supplying your own ErrorAttributes or by subclassing AbstractErrorWebExceptionHandler.

open as a page

Across @ExceptionHandler, WebExceptionHandler, and Boot's error handler, how do you decide which layer owns error handling — and in what order do they see an exception?

level: principalimportance: should knowfreq 35%

basics

~20 s

A controller error is offered first to @ExceptionHandler (local then @ControllerAdvice). If unhandled — or if it came from a filter/functional endpoint — it propagates to the ordered WebExceptionHandler chain, ending in Boot's DefaultErrorWebExceptionHandler (order -1) which renders from ErrorAttributes.

open as a page