Home > Spring > Spring WebFlux > Handling Execptions Global Level in Spring Webflux REST API

Handling Execptions Global Level in Spring Webflux REST API

Recently in one of my Spring Webflux project, one requirement came to handle Exceptions at Global Level. So the question is why we need global handling of exception. The benefit of handling exception at global level is, we can maintain proper error code at one level, this way we will be avoiding duplicate codes inside our spring webflux REST API project.

For that we have to create two classes one which customizes the error attributes and one to implement the exception handling at global level of the spring webflux REST API project.

Customize the error attribute. Below is the code which extends the spring boot reactive default error attribute class and overides its getErrorAttributes method.

  1. GlobalErrorAttributes Class
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.util.Map;

@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes{
    private Logger LOGGER = LoggerFactory.getLogger(getClass());
    private HttpStatus errorstatus = HttpStatus.BAD_REQUEST;
    private String erromessage = "please try after sometime";

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(request, options);

        if (getError(request) instanceof UnAuthorizedException) {
            map.put("status", HttpStatus.UNAUTHORIZED.value());
            map.put("error", HttpStatus.UNAUTHORIZED.getReasonPhrase());
        } else {
            map.put("status", getErrorstatus());
            map.put("message", getErromessage());
        }
        return map;
    }

    public HttpStatus getErrorstatus() {
        return errorstatus;
    }

    public void setErrorstatus(HttpStatus errorstatus) {
        this.errorstatus = errorstatus;
    }

    public String getErromessage() {
        return erromessage;
    }

    public void setErromessage(String erromessage) {
        this.erromessage = erromessage;
    }
}

Here we will set the http status and the error message thrown from our Exception classes. By default we are setting HttpStatus.BAD_REQUEST as http status code of the error and “please try after sometime” as the error message if the exception thrown is not UnAuthorizedException (which we have created in our project).

2. GlobalErrorWebExceptionHandler Class

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;

import java.util.Map;

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
    private Logger LOGGER = LoggerFactory.getLogger(getClass());

    public GlobalErrorWebExceptionHandler(GlobalErrorAttributes g, ApplicationContext applicationContext,
                                          ServerCodecConfigurer serverCodecConfigurer) {
        super(g, new WebProperties.Resources(), applicationContext);
        super.setMessageWriters(serverCodecConfigurer.getWriters());
        super.setMessageReaders(serverCodecConfigurer.getReaders());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono<ServerResponse> renderErrorResponse(final ServerRequest request) {
        ErrorAttributeOptions options = ErrorAttributeOptions
                .defaults()
                .including(ErrorAttributeOptions.Include.MESSAGE)
                .including(ErrorAttributeOptions.Include.STACK_TRACE);
        final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, options);

        //print stack trace before removing from response
        LOGGER.error((String)errorPropertiesMap.get("trace"));
        errorPropertiesMap.remove("trace");

        if (getError(request) instanceof UnAuthorizedException) {
            LOGGER.error("UnAuthorizedException occured > {}", request.requestPath());
            return ServerResponse.status(HttpStatus.UNAUTHORIZED)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(errorPropertiesMap));
        }
        return ServerResponse.status(HttpStatus.BAD_REQUEST)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(errorPropertiesMap));
    }

}

This class extends the AbstractErrorWebExceptionHandler class provided by the spring and custom implements the handling of the Global Eexception.

@Order(-2) is used to give priority boost to the component that ths Spring Boot class DefaultErrorWebExceptionHandler which is of -1 priority. By this we can define what needs to send in the REST API response in case of exception. We are removing “trace” from the error properties as we do not wants to send that to the client side. Before removing the trace, we are printing this in our logger file. Based on the type of Excpetion thrown we can customise the server response to the user, as what we have done in the case of UnAuthorizedException.

3. UnAuthorizedException

public class UnAuthorizedException extends RuntimeException{
    private String message;
    public UnAuthorizedException(String message) {
        super(message);
        this.message = message;
    }
    public UnAuthorizedException() {
    }
}

Sample code where the UnAuthorizedException exception is thrown, this is in our security configuration class where the excepetion is thrown if the JWT token in empty or invalid.

public Authentication getAuthentication(String token) {
        LOGGER.info("JwtUtility getAuthentication token-> {}", token);
        if (StringUtils.isEmpty(token) || !validateToken(token)) {
            throw new UnAuthorizedException("Invalid token");
        }
        return new UsernamePasswordAuthenticationToken(artistUserDetails, token, artistUserDetails.getAuthorities());
    }