Right here in the code, it does not consider the headers of the request to build the headers of the response: https://github.com/spring-projects/spring-framework/blob/958eb0f964ddef1ff1440fd10c5cb850f6ee96db/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java#L89

I'm building a ServerResponse inside my RouterFunction handler as follows:

ServerResponse.ok().body(dto);

This results in an empty Content-Type header of the response, so it falls back to JSON. But if my client wants XML and specifies so in their request's Accept header, they still get JSON.

Here's my workaround:

ServerResponse.ok()
    .contentType(request.headers().accept().stream().findFirst().orElse(null))
    .body(dto);

Now the internals select the correct message converter based on the content type.

Comment From: bclozel

The functional endpoints programming model is about describing routes (predicates on paths, headers and more) and processing matching requests with handlers. This model is more explicit than the annotation variant; it also separates the routing and the handling parts.

As you've noticed, not setting an explicit content-type results in an automatic selection via codecs (the first one that knows how to deal with the response body wins). I'd say that not specifying the content-type might lead to confusing behavior of your application.

I'd say that not setting the content-type automatically is by design.

Your suggestion would only work if the client sends a single value in the accept header, or if the first value is the preferred one. Clients might be sending "*/*" as the only accepted type, or "application/not-supported-by-your-app" as the first one. If this is a reasonable assumption to make for your clients, maybe you could deal with that in a cross-cutting manner with a filter?

The problem is that Content Negotiation is much more involved, since it's considering both client request headers and producible media types from the handler. I'm afraid we can't push such a change.

With that in mind, I'm declining this enhancement request. Feel free to comment this issue if you've got other ideas about helping developers manage this situation.

Thanks!

Comment From: Sam-Kruglov

Thanks for the explanation! I'll update my workaround.

To be honest, I'm still not quite sure why it's better to be so explicit, it's still HTTP communication. I didn't find any special notes in the docs that would say that any magic present in annotation-based endpoints isn't present in functional endpoints. But it does say that "It is an alternative to the annotation-based programming model".

The proper code for it exists in the same module: https://github.com/spring-projects/spring-framework/blob/958eb0f964ddef1ff1440fd10c5cb850f6ee96db/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java#L267-L270

I'll update my workaround to include some copy-pasted code from there...

Comment From: bclozel

It's not better, it's just a different programming model and this is why we can't have 100% behavior parity.

Another example: with Spring MVC, the routing processing (matching an incoming request to a controller handler) is done automatically; it is "scoring" the request using its path and headers to match it with a controller handler. With this model, you can't really tweak the matching process - only annotations on the handler can help.

With the functional model, you have full control over the matching process. The order of predicates is relevant - there is no such thing with the annotation model.

To summarize, there is no single place in the codebase where that matching happens with the functional model. Using a simplified content negotiation might work in your case, but I'm not sure we can implement a general solution here.