Skip to content

Conversation

@wfhartford-wordly
Copy link
Contributor

Micronaut adds a second Content-Type header to an HttpResponse when the result of the controller function is a HttpResponse<Flux<String>> (or other nested data type), and the controller explicitly sets the content type on the HttpResponse. This can be demonstrated with the following simple controller:

@Controller
class VariableContentTypeController {
	@Get("/")
	fun index(): HttpResponse<Flux<String>> {
		return HttpResponse.ok(Flux.just("Hello World!")).contentType("text/plain")
	}
}

See https://github.com/wfhartford-wordly/micronaut-duplicate-content-type-headers for a full project demonstrating the bug.

Calling this function from curl shows that two content type headers are present in the response.

$ curl localhost:8080 -v
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Type: text/plain
< date: Thu, 15 Jan 2026 23:45:04 GMT
< transfer-encoding: chunked
< 
* Connection #0 to host localhost left intact
Hello World!

This PR fixes the bug by changing the RouteExecutor to call the contentType function which sets the header rather than the header function which adds it. I've also included a unit test which verifies the fix.

I first encountered this bug in Micronaut version 4.3.8 and have confirmed that it is present in version 4.10.7 and on the 5.0.x branch.

@cla-assistant
Copy link

cla-assistant bot commented Jan 15, 2026

CLA assistant check
All committers have signed the CLA.

@graemerocher
Copy link
Contributor

do you wish to target the 4.10.x branch or keep this in 5.0.x?

@graemerocher graemerocher requested a review from yawkat January 19, 2026 10:03
@wfhartford-wordly
Copy link
Contributor Author

do you wish to target the 4.10.x branch or keep this in 5.0.x?

I'd love to see this fixed in 4.10.x. Would you like me to create another PR targeting that branch?

@graemerocher
Copy link
Contributor

@wfhartford-wordly a PR targeting 4.10.x would make sense. Thanks

@wfhartford-wordly
Copy link
Contributor Author

#12376 Is the same change targeting the 4.10.x branch.

graemerocher pushed a commit that referenced this pull request Feb 6, 2026
This PR is similar to #12350, but targets the 4.10.x branch insead of 5.0.x.

Micronaut adds a second `Content-Type` header to an HttpResponse when the result of the controller function is a `HttpResponse<Flux<String>>` (or other nested data type), and the controller explicitly sets the content type on the `HttpResponse`. This can be demonstrated with the following simple controller:
```kotlin
@controller
class VariableContentTypeController {
	@get("/")
	fun index(): HttpResponse<Flux<String>> {
		return HttpResponse.ok(Flux.just("Hello World!")).contentType("text/plain")
	}
}
```
See https://github.com/wfhartford-wordly/micronaut-duplicate-content-type-headers for a full project demonstrating the bug.

Calling this function from curl shows that two content type headers are present in the response.
```
$ curl localhost:8080 -v
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.7.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Type: text/plain
< date: Thu, 15 Jan 2026 23:45:04 GMT
< transfer-encoding: chunked
< 
* Connection #0 to host localhost left intact
Hello World!
```

This PR fixes the bug by changing the `RouteExecutor` to call the `contentType` function which _sets_ the header rather than the `header` function which _adds_ it. I've also included a unit test which verifies the fix.

I first encountered this bug in Micronaut version 4.3.8 and have confirmed that it is present in version 4.10.7 and on the 5.0.x branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants