Our team tried to upgrade from Spring Boot 3.4.4 to 3.5.0 and we stumble across a regression that was introduce in Spring-Webflux 2.6.7.

The problem occurs when the defined method use an Interface object as @RequestBody. There is a recent adjustment that was made because of the generic type that wasn't taken in consideration (https://github.com/spring-projects/spring-framework/issues/34793). With the new modification, we now lose the class of the concret object that is hidden behind the interface. When we try to resolve the right Serializer to use, we find nothing and we get the error :

org.springframework.web.reactive.function.client.WebClientRequestException: Content type 'application/json' not supported for bodyType=com.example.demo.fruit.Fruit

After some digging, I found out that we are now setting the body value with its generic parameter type. Because of this, we are taking the interface as the type instead of the concrete class when we try to resolve the right Serializer.

With the version 6.2.7, there is new variable called bodyValueType that retain the object type (https://github.com/spring-projects/spring-framework/blob/v6.2.7/spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java#L80). When this is set, we use this value to determine which Serializer to use (https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/service/invoker/RequestBodyArgumentResolver.java#L113 with the combinaison of https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java#L140).

To what I can see, this is a breaking change 🤔 and that was passed into a minor. Thankfully, there is a workaround that can be done by using @JsonTypeInfo and @JsonSubTypes on the interface.

I will attach a small project with Spring Boot 3.5.0. You can run the 2 tests to see what is going on (1 will pass and 1 will fail). After that, you could change the Spring Boot version to 3.4.5 (that has webflux 6.2.6) and see that the 2 tests are passing.

regressionDemo.zip

Thank you 🙂

Comment From: rstoyanchev

Thanks for the report and sample. The idea with #34793 was that passing more information should be helpful. In other words it wasn't meant to be a breaking change.

On closer look at the example, Jackson returns false from ObjectMapper#canSerialize I think because the interface does not have methods. If at least one method is added, it returns true and serialization works.

I need to have a closer look, and understand what we should do. In the mean time could you comment on the fact the interfaces don't have methods, if that's the actual case?

Comment From: gableg-goto

That's indeed a bit weird to have an interface without method, but that's an actual case. There is a service that accept a lot of command under the same endpoint and we wanted to have 1 http client method to call it. That's the reason behind the empty interface. That way, any object that needs to be send to this endpoint implement the interface and that's it.

But when I look at it, the field type from the example should have a getType method on the interface. That would make more sense if we look at the workaround with the @JsonTypeInfo as it use the type to know which child object to use. So that imply that every implementation of the Interface should have a field type in it.

Taking a step back, I think the "breaking change" is maybe more a fix at end in terms of good programming. The empty interface is probably more a hack than anything else 🤔 What is your thought on this ?

Comment From: rstoyanchev

Indeed it would be better for the interface to reflect any common methods.

Comment From: rstoyanchev

We're still investigating this, but for the moment believe the current behavior is consistent with what we do on the server side.

Comment From: gableg-goto

Okay 👌 with the step back I took, I think this "new" behavior should have been the from the beginning. An empty interface make no sense as the child should implement it and there is nothing to implement.