{"record":{"id":"ef5b4ff629cddb03","repo":"apache/shenyu","slug":"sentinelfallbackexception-for-non-2xx-response-status","errorCode":null,"errorMessage":"SentinelFallbackException for non-2xx response status (defaults to 500 when status is null)","messagePattern":"SentinelFallbackException for non-2xx response status \\(defaults to 500 when status is null\\)","errorType":"http","errorClass":"SentinelFallbackException","httpStatus":null,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-fault-tolerance/shenyu-plugin-sentinel/src/main/java/org/apache/shenyu/plugin/sentinel/SentinelPlugin.java","lineNumber":66,"sourceCode":"    private final FallbackHandler fallbackHandler;\n\n    public SentinelPlugin(final FallbackHandler fallbackHandler) {\n        this.fallbackHandler = fallbackHandler;\n    }\n\n    @Override\n    protected Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {\n        final ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);\n        Objects.requireNonNull(shenyuContext);\n        String resourceName = CacheKeyUtils.INST.getKey(rule);\n        SentinelHandle sentinelHandle = SentinelRuleHandle.CACHED_HANDLE.get().obtainHandle(resourceName);\n        if (Objects.isNull(sentinelHandle)) {\n            return chain.execute(exchange);\n        }\n        sentinelHandle.checkData();\n        exchange.getAttributes().put(Constants.WATCHER_HTTP_STATUS, (Consumer<HttpStatus>) status -> {\n            if (Objects.isNull(status) || !status.is2xxSuccessful()) {\n                throw new SentinelFallbackException(Objects.isNull(status) ? HttpStatus.INTERNAL_SERVER_ERROR : status);\n            }\n        });\n        return chain.execute(exchange).transform(new SentinelReactorTransformer<>(resourceName)).onErrorResume(throwable ->\n                fallbackHandler.fallback(exchange, UriUtils.createUri(sentinelHandle.getFallbackUri()), throwable)).doFinally(monoV -> {\n                    final Consumer<HttpStatusCode> consumer = exchange.getAttribute(Constants.METRICS_SENTINEL);\n                    Optional.ofNullable(consumer).ifPresent(c -> c.accept(exchange.getResponse().getStatusCode()));\n                }\n        );\n    }\n\n    @Override\n    public String named() {\n        return PluginEnum.SENTINEL.getName();\n    }\n\n    @Override\n    public int getOrder() {\n        return PluginEnum.SENTINEL.getCode();","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-fault-tolerance/shenyu-plugin-sentinel/src/main/java/org/apache/shenyu/plugin/sentinel/SentinelPlugin.java#L48-L84","documentation":"The Sentinel plugin registers a watcher consumer on the exchange that throws SentinelFallbackException whenever the final HTTP status is not 2xx (defaulting to 500 for null). The exception drives Sentinel's error accounting and triggers the configured fallback handler via onErrorResume.","triggerScenarios":"A request matching a Sentinel resource completes with a non-2xx response status (or null status) — the consumer placed in exchange attributes under WATCHER_HTTP_STATUS throws, and doExecute's onErrorResume routes to fallbackHandler.fallback with the rule's fallbackUri.","commonSituations":"Downstream API returning 404/500 while a Sentinel rule with fallback is attached; no fallbackUri configured so the client sees the fallback error directly; null status races producing the default 500.","solutions":["Set sentinelHandle's fallbackUri so non-2xx responses are handled by a controlled fallback endpoint.","Remediate the downstream service causing the non-2xx status.","Exclude expected 4xx statuses from fallback if business errors should pass through unchanged.","If the default 500 path fired, investigate why the response status was null when the watcher ran."],"exampleFix":"// before\n{\"sentinelHandle\": {\"fallbackUri\": \"\"}}\n// after\n{\"sentinelHandle\": {\"fallbackUri\": \"/fallback/sentinel\"}}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"return chain.execute(exchange)\n  .transform(new SentinelReactorTransformer<>(resource))\n  .onErrorResume(SentinelFallbackException.class, e ->\n      fallbackHandler.fallback(exchange, UriUtils.createUri(fallbackUri), e));","preventionTips":["Set fallbackUri on every sentinel rule that watches HTTP status.","Alert on downstream non-2xx rates before breakers trip.","Exclude deliberate 4xx business statuses from triggering fallback where appropriate."],"tags":["sentinel","fault-tolerance","http-status"],"backgroundTag":"http-non-2xx-response","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}