GlobalErrorHandler.java

package com.dani.bff.error;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;

/**
 * Converts uncaught application exceptions into structured API errors.
 */
@RestControllerAdvice
public class GlobalErrorHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalErrorHandler.class);

    /**
     * Handles downstream failures that escape resilience fallbacks.
     *
     * @param exception downstream exception
     * @param exchange current request exchange
     * @return structured error response
     */
    @ExceptionHandler(DownstreamServiceException.class)
    public ResponseEntity<ApiError> handleDownstreamFailure(
            DownstreamServiceException exception,
            ServerWebExchange exchange) {
        log.warn("Downstream service failed: service={} status={}", exception.getServiceName(), exception.getStatusCode());
        return error(HttpStatus.BAD_GATEWAY, "A downstream service could not complete the request", exchange);
    }

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<ApiError> handleResponseStatus(ResponseStatusException exception, ServerWebExchange exchange) {
        HttpStatus status = HttpStatus.valueOf(exception.getStatusCode().value());
        return error(status, exception.getReason() != null ? exception.getReason() : status.getReasonPhrase(), exchange);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleUnexpected(Exception exception, ServerWebExchange exchange) {
        log.error("Unexpected BFF failure", exception);
        return error(HttpStatus.INTERNAL_SERVER_ERROR, "The BFF could not complete the request", exchange);
    }

    private static ResponseEntity<ApiError> error(HttpStatus status, String message, ServerWebExchange exchange) {
        String path = exchange.getRequest().getPath().pathWithinApplication().value();
        return ResponseEntity.status(status).body(ApiError.of(status, message, path));
    }
}