spring-cloud / spring-cloud/spring-cloud-gateway
UnsupportedOperationException in NettyRoutingFilter when handling immutable ReadOnlyHttpHeaders
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 4.9k
- Forks
- 3.5k
- Avg merge
- 20h 57m
- Merged PRs (30d)
- 8
Description
Describe the bug
When routing requests through Spring Cloud Gateway, the NettyRoutingFilter attempts to remove headers from a ReadOnlyHttpHeaders instance. This results in a java.lang.UnsupportedOperationException.
Although the response is still committed successfully (200 OK), the error is logged, which is misleading and suggests unexpected behavior in the filter chain.
Spring Boot version: 4.0.6/4.1.1
Spring Cloud Gateway: 2025.1.1/20.25.1.2
Sample
No sample application but i can try to create if need.
Logs:
2026-08-26T08:37:40.345Z TRACE 1 --- [gateway] [or-http-epoll-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [37da73a0-12] HTTP GET "/AAA/BBB", headers={masked}
2026-08-26T08:37:40.421Z INFO 1 --- [gateway] [or-http-epoll-3] c.c.a.hub.gateway.config.LoggingFilter : Incoming request https://example.com/AAA/BBB is routed to id: BBB-ui, uri: http://BBB-ui:80/AAA/BBB
2026-08-26T08:37:40.445Z ERROR 1 --- [gateway] [or-http-epoll-3] c.c.a.h.g.h.TopLevelExceptionHandler : Caught exception
java.lang.UnsupportedOperationException
at org.springframework.http.ReadOnlyHttpHeaders.remove(ReadOnlyHttpHeaders.java:166) ~[spring-web-7.0.9.jar!/:7.0.9]
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
Error has been observed at the following site(s):
*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter@30704f85
*__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter@43115b17
*__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter@79976e07
*__checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter@4f229e87
*__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter@2e66a2b
*__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter@2475bbb3
*__checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter@12284f0d
*__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter@21cf894
*__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter@28fbda1c
*__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter@682d6318
*__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter@5515313b
*__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter@2c02c49e
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy@32328dc4
*__checkpoint ⇢ HTTP GET "/AAA/BBB" [ExceptionHandlingWebHandler]
Original Stack Trace:
at org.springframework.http.ReadOnlyHttpHeaders.remove(ReadOnlyHttpHeaders.java:166) ~[spring-web-7.0.9.jar!/:7.0.9]
at org.springframework.cloud.gateway.filter.NettyRoutingFilter.lambda$filter$4(NettyRoutingFilter.java:182) ~[spring-cloud-gateway-server-webflux-5.0.2.jar!/:5.0.2]
at reactor.netty.http.client.HttpClientFinalizer.lambda$responseConnection$4(HttpClientFinalizer.java:102) ~[reactor-netty-http-1.3.7.jar!/:1.3.7]
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:164) ~[reactor-core-3.8.7.jar!/:3.8.7]
at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:100) ~[reactor-core-3.8.7.jar!/:3.8.7]
at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onNext(FluxRetryWhen.java:179) ~[reactor-core-3.8.7.jar!/:3.8.7]
at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:177) ~[reactor-core-3.8.7.jar!/:3.8.7]
at reactor.netty.http.client.HttpClientConnect$HttpIOHandlerObserver.onStateChange(HttpClientConnect.java:460) ~[reactor-netty-http-1.3.7.jar!/:1.3.7]
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:736) ~[reactor-netty-core-1.3.7.jar!/:1.3.7]
at reactor.netty.resources.DefaultPooledConnectionProvider$DisposableAcquire.onStateChange(DefaultPooledConnectionProvider.java:229) ~[reactor-netty-core-1.3.7.jar!/:1.3.7]
at reactor.netty.resources.DefaultPooledConnectionProvider$PooledConnection.onStateChange(DefaultPooledConnectionProvider.java:491) ~[reactor-netty-core-1.3.7.jar!/:1.3.7]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:858) ~[reactor-netty-http-1.3.7.jar!/:1.3.7]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115) ~[reactor-netty-core-1.3.7.jar!/:1.3.7]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:434) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:361) ~[netty-codec-base-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:348) ~[netty-codec-base-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:470) ~[netty-codec-base-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-base-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:249) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:804) ~[netty-transport-classes-epoll-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.handle(AbstractEpollChannel.java:487) ~[netty-transport-classes-epoll-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.epoll.EpollIoHandler$DefaultEpollIoRegistration.handle(EpollIoHandler.java:349) ~[netty-transport-classes-epoll-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.epoll.EpollIoHandler.processReady(EpollIoHandler.java:548) ~[netty-transport-classes-epoll-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.epoll.EpollIoHandler.run(EpollIoHandler.java:491) ~[netty-transport-classes-epoll-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196) ~[netty-transport-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1204) ~[netty-common-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.2.17.Final.jar!/:4.2.17.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.2.17.Final.jar!/:4.2.17.Final]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
2026-08-26T08:37:40.466Z ERROR 1 --- [gateway] [or-http-epoll-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [37da73a0-12] Error [java.lang.UnsupportedOperationException] for HTTP GET "/AAA/BBB", but ServerHttpResponse already committed (200 OK)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at NettyRoutingFilter.lambda$filter$4 around line 182, where the stack trace shows removal from ReadOnlyHttpHeaders, and compare this with the request routing flow described in the issue. Reproduce the request if possible, then verify that routing still returns 200 OK without logging an UnsupportedOperationException or a misleading committed-response error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring, spring-boot
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100