2

I have tried all 3 solutions suggested in what is the right way to handle errors in spring-webflux, but WebExceptionHandler is not getting called. I am using Spring Boot 2.0.0.M7. Github repo here

@Configuration
class RoutesConfiguration {

  @Autowired
  private lateinit var testService: TestService

  @Autowired
  private lateinit var globalErrorHandler: GlobalErrorHandler

  @Bean
  fun routerFunction():

    RouterFunction<ServerResponse> = router {
    ("/test").nest {

      GET("/") {
        ServerResponse.ok().body(testService.test())
      }
    }
  }


} 


@Component
class GlobalErrorHandler() : WebExceptionHandler {

  companion object {
    private val log = LoggerFactory.getLogger(GlobalErrorHandler::class.java)
  }

  override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {

    log.info("inside handle")

    /* Handle different exceptions here */
    when(ex!!) {
      is ClientException -> exchange!!.response.statusCode = HttpStatus.BAD_REQUEST
      is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
    }

    return Mono.empty()
  }
}

UPDATE:

When I change Spring Boot version to 2.0.0.M2, the WebExceptionHandler is getting called. Do I need to do something for 2.0.0.M7?

SOLUTION:

As per Brian's suggestion, it worked as

@Bean
@Order(-2)
fun globalErrorHandler() = GlobalErrorHandler()
Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
jaks
  • 4,407
  • 9
  • 53
  • 68

2 Answers2

16

You can provide your own WebExceptionHandler, but you have to order it relatively to others, otherwise they might handle the error before yours get a chance to try.

  • the DefaultErrorWebExceptionHandler provided by Spring Boot for error handling (see reference documentation) is ordered at -1
  • the ResponseStatusExceptionHandler provided by Spring Framework is ordered at 0

So you can add @Order(-2) on your error handling component, to order it before the existing ones.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • 1
    Spring boot built-in `AbstractErrorWebExceptionHandler` seems more friendly for global error handling of web pages based applications. For REST applications, I think it is better if providing a `@RestControllerAdvice` like solution to set http status and entity. – Hantsy Jan 01 '18 at 02:56
  • I can't make RestControllerAdvice to catch errors before DefaultErrorWebExceptionHandler or a custom WebExceptionHandler... – brebDev Aug 15 '18 at 09:14
  • @Order(-123) definitely should solve a problem to ask spring context apply bean earlier – Maksim Kostromin Dec 10 '19 at 14:23
5

An error response should have standard payload info. This can be done by extending AbstractErrorWebExceptionHandler

ErrorResponse: Data Class

data class ErrorResponse(
    val timestamp: String,
    val path: String,
    val status: Int,
    val error: String,
    val message: String
)

ServerResponseBuilder: 2 different methods to build an error response

  • default: handle standard errors
  • webClient: handle webClient exceptions (WebClientResponseException), not for this case

    class ServerResponseBuilder(
            private val request: ServerRequest, 
            private val status: HttpStatus) {
    
        fun default(): Mono<ServerResponse> =
            ServerResponse
                    .status(status)
                    .body(BodyInserters.fromObject(ErrorResponse(
                            Date().format(),
                            request.path(),
                            status.value(),
                            status.name,
                            status.reasonPhrase)))
    
        fun webClient(e: WebClientResponseException): Mono<ServerResponse> =
            ServerResponse
                    .status(status)
                    .body(BodyInserters.fromObject(ErrorResponse(
                            Date().format(),
                            request.path(),
                            e.statusCode.value(),
                            e.message.toString(),
                            e.responseBodyAsString)))
    }
    

GlobalErrorHandlerConfiguration: Error handler

@Configuration
@Order(-2)
class GlobalErrorHandlerConfiguration @Autowired constructor(
        errorAttributes: ErrorAttributes,
        resourceProperties: ResourceProperties,
        applicationContext: ApplicationContext,
        viewResolversProvider: ObjectProvider<List<ViewResolver>>,
        serverCodecConfigurer: ServerCodecConfigurer) :
        AbstractErrorWebExceptionHandler(
                errorAttributes,
                resourceProperties,
                applicationContext
        ) {

    init {
        setViewResolvers(viewResolversProvider.getIfAvailable { emptyList() })
        setMessageWriters(serverCodecConfigurer.writers)
        setMessageReaders(serverCodecConfigurer.readers)
    }

    override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> = 
        RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { response(it, errorAttributes) })

    private fun response(request: ServerRequest, errorAttributes: ErrorAttributes?): Mono<ServerResponse> =
        ServerResponseBuilder(request, status(request, errorAttributes)).default()

    private fun status(request: ServerRequest, errorAttributes: ErrorAttributes?) =
        HttpStatus.valueOf(errorAttributesMap(request, errorAttributes)["status"] as Int)

    private fun errorAttributesMap(request: ServerRequest, errorAttributes: ErrorAttributes?) =
        errorAttributes!!.getErrorAttributes(request, false)
}
RGAT
  • 486
  • 3
  • 13
Alberto Galiana
  • 201
  • 5
  • 7