Search code examples
springspring-bootkotlinspring-webfluxexceptionhandler

Spring WebFlux Reactive Error Handling - Kotlin


I wanted to find a way to create an exception handler for Spring Webflux, and I checked the documentation for that

https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/html/web.html#web.reactive.webflux.error-handling

I used the Kotlin code example to build the an exception Handler, but I faced several errors with.

import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class MyErrorWebExceptionHandler(errorAttributes: ErrorAttributes?, resources: WebProperties.Resources?,
    applicationContext: ApplicationContext?) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {

    override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
        return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
    }

    private fun acceptsXml(request: ServerRequest): Boolean {
        return request.headers().accept().contains(MediaType.APPLICATION_XML)
    }

    fun handleErrorAsXml(request: ServerRequest?): Mono<ServerResponse> {
        val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
        // ... additional builder calls
        return builder.build()
    }

}

how this can be done?


Solution

  • I found out a way to do that, after spending some hours here and there. I found out that I have to create a beam for the web properties resources.

    @Component
    class Resources {
    
        @Bean
        fun webResources(): WebProperties.Resources {
            return WebProperties.Resources()
        }
    
    }
    

    Then I can create the exception handler with the suspend functions that can be used to send back the error messages.

    package com.saher.testing.exception
    
    import org.springframework.boot.autoconfigure.web.WebProperties
    import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
    import org.springframework.boot.web.reactive.error.ErrorAttributes
    import org.springframework.context.ApplicationContext
    import org.springframework.http.HttpStatus
    import org.springframework.http.codec.ServerCodecConfigurer
    import org.springframework.stereotype.Component
    import org.springframework.web.reactive.function.server.*
    
    @Component
    class GlobalErrorWebExceptionHandler(
        errorAttributes: ErrorAttributes,
        resourceProperties: WebProperties.Resources,
        applicationContext: ApplicationContext,
        serverCodecConfigurer: ServerCodecConfigurer
    ) : AbstractErrorWebExceptionHandler(
        errorAttributes,
        resourceProperties,
        applicationContext
    ) {
    
        init {
            super.setMessageWriters(serverCodecConfigurer.writers)
            super.setMessageReaders(serverCodecConfigurer.readers)
        }
    
        override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
            return coRouter {
                GET("/exception", ::exceptionHandler)
            }
        }
    
        private suspend fun exceptionHandler(serverRequest: ServerRequest): ServerResponse {
            return ServerResponse
                .status(HttpStatus.BAD_REQUEST)
                .bodyValueAndAwait(
                    getError(serverRequest).localizedMessage
                )
        }
    
    }
    

    of course you can make a cleaner code by separating the GlobalErrorWebExceptionHandler from the exceptionHandler suspend functions that will be used to handle send back the error messages.

    Regards