apache/shenyu · error · CircuitBreakerStatusCodeException

CircuitBreakerStatusCodeException for non-2xx response…

Error message

CircuitBreakerStatusCodeException for non-2xx response status (defaults to 500 when status is null)

What it means

The resilience4j circuit-breaker plugin treats any non-2xx gateway response as a failure signal: inside doOnSuccess it throws CircuitBreakerStatusCodeException (carrying the response status, or 500 when null) and resets the status so the breaker records the error and the fallback URI can serve the response.

Solutions

  1. Configure a fallbackUri on the resilience4j rule so non-2xx responses get a controlled fallback instead of an error page.
  2. Fix the upstream service that is returning the non-2xx status if the error is unexpected.
  3. If 4xx business statuses should not count as circuit failures, narrow the rule or adjust plugin behavior so only 5xx trigger the breaker.
  4. Check why the response status was null (response committed/reset race) if the message reports the default 500.

Example fix

// before (rule without fallback)
{"fallBackUri": null}
// after
{"fallBackUri": "/fallback/circuitBreaker"}
Defensive patterns

Strategy: fallback

Try / catch

return chain.execute(exchange)
  .onErrorResume(CircuitBreakerStatusCodeException.class, e ->
      fallbackHandler.fallback(exchange, UriUtils.createUri(fallbackUri), e));

Prevention

When it happens

Trigger: A request matching a resilience4j rule completes but exchange.getResponse().getStatusCode() is not 2xxSuccessful (e.g. downstream returned 404/502), or the status is null at doOnSuccess time — thrown from combined() called by doExecute.

Common situations: Backend service returns 4xx/5xx for business errors and the operator expects the original status to pass through, but the fallback URI intercepts; broken downstream deployments causing 502s; misconfigured fallback URI leading to repeated breaker trips.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/90159bff95c209fe. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-fault-tolerance/shenyu-plugin-resilience4j/src/main/java/org/apache/shenyu/plugin/resilience4j/Resilience4JPlugin.java:90

            return combined(exchange, chain, rule);
        }
        return rateLimiter(exchange, chain, rule);
    }

    private Mono<Void> rateLimiter(final ServerWebExchange exchange, final ShenyuPluginChain chain, final RuleData rule) {
        return ratelimiterExecutor.run(
                chain.execute(exchange), fallback(ratelimiterExecutor, exchange, null), Resilience4JBuilder.build(rule))
                .onErrorResume(throwable -> ratelimiterExecutor.withoutFallback(exchange, throwable));
    }

    private Mono<Void> combined(final ServerWebExchange exchange, final ShenyuPluginChain chain, final RuleData rule) {
        Resilience4JConf conf = Resilience4JBuilder.build(rule);
        return combinedExecutor.run(
                chain.execute(exchange).doOnSuccess(v -> {
                    HttpStatusCode status = exchange.getResponse().getStatusCode();
                    if (Objects.isNull(status) || !status.is2xxSuccessful()) {
                        exchange.getResponse().setStatusCode(null);
                        throw new CircuitBreakerStatusCodeException(Objects.isNull(status) ? HttpStatus.INTERNAL_SERVER_ERROR : status);
                    }
                }), fallback(combinedExecutor, exchange, conf.getFallBackUri()), conf);
    }

    private Function<Throwable, Mono<Void>> fallback(final Executor executor,
                                                     final ServerWebExchange exchange, final String uri) {
        return throwable -> executor.fallback(exchange, UriUtils.createUri(uri), throwable).doFinally(monoV -> {
            final Consumer<HttpStatusCode> consumer = exchange.getAttribute(Constants.METRICS_RESILIENCE4J);
            Optional.ofNullable(consumer).ifPresent(c -> c.accept(exchange.getResponse().getStatusCode()));
        });
    }

    @Override
    public int getOrder() {
        return PluginEnum.RESILIENCE4J.getCode();
    }

    @Override

View on GitHub (pinned to 567142e072)