Exception Handling Techniques in Spring WebFlux

Answered using GPT-4o-mini by Java Q&A Hub

Question:

Exception handling in Spring Webflux?

Answer:

In Spring WebFlux, exception handling can be managed in several ways, allowing you to handle errors in a reactive programming style. Here are some common approaches to handle exceptions in a Spring WebFlux application:

1. Using @ControllerAdvice

You can create a global exception handler using the @ControllerAdvice annotation. This allows you to handle exceptions thrown by any controller in a centralized manner.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import reactor.core.publisher.Mono;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public Mono<ResponseEntity<String>> handleCustomException(CustomException ex) {
        return Mono.just(ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(ex.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public Mono<ResponseEntity<String>> handleGenericException(Exception ex) {
        return Mono.just(ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("An unexpected error occurred"));
    }
}

2. Using WebExceptionHandler

For more fine-grained control, you can implement the WebExceptionHandler interface. This allows you to handle exceptions at a lower level.

import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

@Component
@Order(-2) // Ensure this handler is called before the default handlers
public class CustomWebExceptionHandler implements WebExceptionHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        if (ex instanceof CustomException) {
            return ServerResponse
                    .status(HttpStatus.BAD_REQUEST)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(ex.getMessage())
                    .flatMap(response -> response.writeTo(exchange, new Context()));
        }
        return ServerResponse
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .bodyValue("An unexpected error occurred")
                .flatMap(response -> response.writeTo(exchange, new Context()));
    }
}

3. Using onErrorResume and doOnError

In your reactive pipelines, you can use operators like onErrorResume and doOnError to handle exceptions.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class MyController {

    @GetMapping("/example")
    public Mono<String> example() {
        return someServiceCall()
                .onErrorResume(CustomException.class, ex -> Mono.just("Handled Custom Exception: " + ex.getMessage()))
                .doOnError(ex -> System.err.println("Error occurred: " + ex.getMessage()));
    }

    private Mono<String> someServiceCall() {
        // Simulate a service call that may throw an exception
        return Mono.error(new CustomException("Something went wrong"));
    }
}

4. Custom Error Attributes

You can also customize the error response by implementing ErrorAttributes. This is useful if you want to modify the default error response structure.

import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Mono;

import java.util.Map;

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
        // Customize the error attributes here
        errorAttributes.put("customMessage", "This is a custom error message");
        return errorAttributes;
    }
}

Summary

These are some of the common ways to handle exceptions in Spring WebFlux. Depending on your application's requirements, you can choose the approach that best fits your needs. Using @ControllerAdvice is often the simplest way to handle exceptions globally, while WebExceptionHandler provides more control over the error handling process.