spring-cloud / spring-cloud/spring-cloud-gateway
Cannot modify headers on After Filter when response is created with `ServerResponse.ok().build()`
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 a route handler returns a ServerResponse created via ServerResponse.ok().build(). The headers exposed to ServerResponse.headers() are immutable (ReadOnlyHttpHeaders).
Attempting to modify response headers inside an After Filter (even with predefined AfterFilterFunctions.addResponseHeader() function) throws UnsupportedOperationException with stacktrace:
java.lang.UnsupportedOperationException
at org.springframework.http.ReadOnlyHttpHeaders.addAll(ReadOnlyHttpHeaders.java:102) ~[spring-web-6.2.18.jar:6.2.18]
at org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.lambda$addResponseHeader$0(AfterFilterFunctions.java:59) ~[spring-cloud-gateway-server-mv
c-4.3.4.jar:4.3.4]
--- snip ---
However, the same .after() filter works correctly when using predefined handlers such as HandlerFunctions.http()
Expected behavior
AfterFilterFunctions.addResponseHeader() (and any After Filter Functions that modifies headers) should be able to modify response headers consistently for ServerResponse returned from route handlers.
Alternatively, if immutable headers are intentional, the framework should avoid mutating response.headers() directly inside AfterFilterFunctions.addResponseHeader().
Sample
Minimal, reproducible code example
fooRoute(failing route) returns 500 Internal Server Error
public RouterFunction<ServerResponse> fooRoute() {
return route("fooRoute")
.GET(path("/foo"), req -> ServerResponse.ok().build())
.after(addResponseHeader("X-Test", "TestFoo"))
.build();
}
barRoute(working route) returns 200 OK with expected response header
public RouterFunction<ServerResponse> barRoute() {
return route("barRoute")
.GET(path("/bar"), http())
.before(uri("http://httpbin.org"))
.before(setPath("/get"))
.after(addResponseHeader("X-Test", "TestBar"))
.build();
}
Java version: 21
Spring boot: 3.5.14
Spring cloud version: 2025.0.2
Spring cloud gateway web MVC: 4.3.4
This issue appears specific to response created directly from route handlers, since proxied responses trough HandlerFunctions.http() behave as expected.
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 AfterFilterFunctions.addResponseHeader(), identified in the stack trace, and reproduce the difference between the fooRoute ServerResponse.ok().build() route and the barRoute HandlerFunctions.http() route. Inspect how response headers are exposed in this path and verify the existing after-filter behavior. Done means X-Test is added without UnsupportedOperationException for direct ServerResponse responses while the proxy route continues to work.
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
- Clearly specified
- Newbie friendliness
- 76/100