wujek
wujek

Reputation: 11040

WebFlux @ExceptionHandler in a @ControllerAdvice not called for exception thrown in WebFilter

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

Answers (2)

Toerktumlare
Toerktumlare

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

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/WebExceptionHandler.html

https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java

https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Upvotes: 3

vabatch_dev
vabatch_dev

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

Related Questions