Cloud Native 5 min read

Mastering Global Exception Handling in Spring Cloud Gateway

This article explains why traditional @ControllerAdvice fails in Spring Cloud Gateway, introduces the built‑in ExceptionHandlingWebHandler and DefaultErrorWebExceptionHandler, and shows how to override them with a custom GlobalExceptionConfiguration for robust reactive error handling.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Mastering Global Exception Handling in Spring Cloud Gateway

Why Global Exception Handling Is Needed

In a traditional Spring Boot application we use

@ControllerAdvice

to handle exceptions globally and wrap responses uniformly.

<code>// excerpt from spring cloud alibaba console module
@ControllerAdvice
public class ConsoleExceptionHandler {
    @ExceptionHandler(AccessException.class)
    private ResponseEntity<String> handleAccessException(AccessException e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
    }
}
</code>

For example, a database exception can be caught by

@ControllerAdvice

and returned to the client.

However, in a micro‑service architecture, when a gateway fails to forward a request to a downstream service (e.g., network error, service unavailable), the request never reaches the application, so

@ControllerAdvice

is ineffective because the traffic never hits the service.

When all route predicates fail (404), the gateway returns the default Spring Boot error page, which cannot be handled by

@ControllerAdvice

because Spring Cloud Gateway is built on WebFlux.

Solution

Default Processing Flow

ExceptionHandlingWebHandler, as the core WebHandler of Spring Cloud Gateway, performs exception filtering.

<code>public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        Mono<Void> completion;
        try {
            completion = super.handle(exchange);
        } catch (Throwable ex) {
            completion = Mono.error(ex);
        }
        // invoke global WebExceptionHandler chain
        for (WebExceptionHandler handler : this.exceptionHandlers) {
            completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
        }
        return completion;
    }
}
</code>

Default implementation:

DefaultErrorWebExceptionHandler
<code>public class DefaultErrorWebExceptionHandler {
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        // decide response based on Accept header; browsers get HTML page
        return route(acceptsTextHtml(), this::renderErrorView)
               .andRoute(all(), this::renderErrorResponse);
    }
}
</code>
<code>// simulate Accept header
curl --location --request GET 'http://localhost:9999/adminx/xx' \
     --header 'Accept: application/json'
{"timestamp":"2020-05-24 18:09:24","path":"/adminx/xx","status":404,"error":"Not Found","message":null,"requestId":"083c48e3-2"}
</code>

Override ErrorWebExceptionHandler

<code>/**
 * Global exception handler for the gateway (WebFlux environment).
 * Must have lower priority than ResponseStatusExceptionHandler.
 */
@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }
        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
            } catch (JsonProcessingException e) {
                log.warn("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}
</code>

Summary

The overridden

DefaultErrorWebExceptionHandler

must have lower priority than the built‑in

ResponseStatusExceptionHandler

to correctly map error classes to response codes.

Additional extensions (e.g.,

SentinelBlockExceptionHandler

) can be integrated, but overall they behave similarly to the default error handling.

Environment: Spring Cloud Hoxton.SR4 & Spring Boot 2.3.0.

Full implementation code reference: https://gitee.com/log4j/pig

JavaSpring BootWebFluxError HandlingSpring Cloud GatewayGlobal Exception Handling
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.