Reputation: 11040
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,
)
Upvotes: 0
Views: 4732
Reputation: 14712
This is a problem that has to be solved in different ways depending on the user case.
If you dont have spring security, then exceptions that are thrown in filters in WebFlux are handled in WebExceptionHandler
s. There are 2 implementations of this class and you can provide your own to handle custom exceptions.
If you throw a ResponseStatusException
which is a spring exception it will get handled in the ResponseStatusExceptionHandler
If you on the other hand want to implement handling on a global level, spring webflux provides a DefaultErrorWebExceptionHandler
which will handle all exception not caught be other handlers.
You can implement your own but this class is quite verbose so Spring recommends that you can extend the AbstractErrorWebExceptionHandler
and implement your own global handler.
Catching exceptions in filters is not super easy as filters are registered with the underlying server and are not really part of your application.
Thats the reason for the quote you took from the documentation.
in spring security they have a different approach they have a ExceptionTranslationFilter
which is a filter that is infront of all filters that catches exceptions that are subclasses of AccessDeniedException
and AuthenticationException
. Why they have chosen this solution is a very long answer so im not going to talk about it.
Upvotes: 3
Reputation: 115
Can you tell a little bit more about the Web Filter and the exception you are facing.
That could be resposability of the WebFilter.
For example, if you have a Security Web Filter, you can handle exceptions by adding "exceptionHandling()" to the builder:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.exceptionHandling()
// customize how to request for authentication
.accessDeniedHandler(yourClass);
.and()
.authorizeExchange()
.pathMatchers(PUBLIC_ACCESS_PATHS).permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt();
return http.build();
}
Upvotes: 1