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
- Configure a fallbackUri on the resilience4j rule so non-2xx responses get a controlled fallback instead of an error page.
- Fix the upstream service that is returning the non-2xx status if the error is unexpected.
- If 4xx business statuses should not count as circuit failures, narrow the rule or adjust plugin behavior so only 5xx trigger the breaker.
- 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
- Always configure fallBackUri on resilience4j rules.
- Monitor upstream 4xx/5xx rates; any non-2xx counts toward the breaker.
- Decide explicitly whether business 4xx should count as failures and adjust rules accordingly.
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
- Http StatusCode " + responseEntity.getStatusCode()
- SentinelFallbackException for non-2xx response status…
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();
}
@OverrideView on GitHub (pinned to 567142e072)