In my WebFlux application (annotated controllers) I noticed that if an exception is thrown from a @RestController
method, an @ExceptionHandler
within a @ControllerAdvice
is executed and the exception is correctly handled. However, if the exception happens earlier, e.g. in a WebFilter
, the handler is not executed.
I read the WebFlux docs and found this: https://docs.spring.io/spring-framework/docs/5.3.21/reference/html/web-reactive.html#webflux-dispatcher-exceptions:
However, keep in mind that, in WebFlux, you cannot use a @ControllerAdvice to handle exceptions that occur before a handler is chosen.
So I assume the problem is that in when the filter is running, the handler for the request hasn't been chosen yet. I am not sure if this assumption is correct. Also, if this is indeed the issue, the documentation doesn't explain what to do instead.
What is the reason for the behavior I'm observing and what do I need to do to for the exception handler to be called?
Here is the code:
package com.test
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.*
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
fun main(vararg args: String) {
runApplication<TestApplication>(*args)
}
@EnableWebFlux
@SpringBootApplication
class TestApplication
@RequestMapping
@RestController
class TestController {
@GetMapping(
"/test",
produces = [MediaType.APPLICATION_JSON_VALUE],
)
fun test(): Map<String, Any?> {
throw IllegalArgumentException("exception from controller")
// return mapOf("all" to "good")
}
}
@Component
class ThrowingFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// throw IllegalArgumentException("exception from filter")
return chain.filter(exchange)
}
}
@RestControllerAdvice
class TestExceptionHandler {
@ExceptionHandler
fun handle(ex: IllegalArgumentException): ErrorInfo {
return ErrorInfo(ex.message ?: "bar")
}
}
data class ErrorInfo(
val errorMessage: String,
)