0

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,
)
wujek
  • 10,112
  • 12
  • 52
  • 88

2 Answers2

1

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();
}
vabatch_dev
  • 115
  • 4
  • The filter is mine, contains custom logic and it sometimes needs to throw an exception. It is not related to security. It is possible that I need to handle the exception in the filter myself and it can be done, but I hoped there would be a way for it to bubble up to the global handler and have the response writing code only once. (Yes I can add a helper function that is called from both the handler and the filter.) – wujek Aug 01 '22 at 19:15
  • Thanks for the mention of `exceptionHandling`, I didn't know it. – wujek Aug 01 '22 at 19:24
  • seems that you can also do it, check https://stackoverflow.com/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring/34633687#34633687 and https://stackoverflow.com/questions/70873751/how-to-globally-handle-errors-thrown-from-webfilter-in-spring-webflux :) – vabatch_dev Aug 01 '22 at 22:51
  • I know I can handle the exception in the filter, but the whole point is that I would like the already existing handler to take care of it without my repeating myself. – wujek Aug 02 '22 at 13:03
1

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

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
  • I found these classes as well but it seems to me that if I use AbstractErrorWebExceptionHandler or the superclasses I will need to add the `if (ex is XxxException)` myself, something that a @ControllerAdvice currently does for me, is this correct? – wujek Aug 02 '22 at 13:26
  • I find it fascinating that both answers somehow assume this is security related :). Why? – wujek Aug 02 '22 at 13:27
  • @ControllerAdvice will not handle exceptions in filters so you can add those how many you like but they will not catch exceptions in filters. Also none of us are assuming that this is security related its just that many that read these answers in the future want complete answers. – Toerktumlare Aug 02 '22 at 13:43
  • Let me ask the last question - suppose I have many filters and they throw different exceptions that I want to handle using the AbstractErrorWebExceptionHandler subclass, but each slightly different - I would need the 'instanceof' (Java) or 'is' (Kotlin) checks somewhere, right? – wujek Aug 02 '22 at 13:56
  • yes or do a base exception and all exceptions iherit from that exception and instanceof it. Do not ask questions in comments im leaaving now – Toerktumlare Aug 02 '22 at 15:49
  • https://meta.stackoverflow.com/questions/258206/what-is-a-help-vampire – Toerktumlare Aug 02 '22 at 16:04
  • Thanks a lot for calling names, although I don't understand it, I think my questions all relate to the topic. – wujek Aug 02 '22 at 16:05
  • it doesnt matter if they are on topic, stack overflow is not a forum. Its a Q&A site, these are comments you ask ONE question and get ONE answer – Toerktumlare Aug 02 '22 at 16:06
  • we are friendly i answered your question, you are not following the rules and you are taking advantage of our friendlyness. i cost $150 an hour at my job, and im helping you for free and now you are calling me not friendly..... – Toerktumlare Aug 02 '22 at 16:10