Description
I initially reported this here and here at the Spring Framework project, but the spring.mvc.converters.preferred-json-mapper
property is apparently specific to Spring Boot, so I'm reporting it here.
Summary of the problem: when certain serde frameworks, notably kotlinx.serialization
, are detected in the classpath by Spring, then the Spring Boot configuration property spring.mvc.converters.preferred-json-mapper
(defined in HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY
) is effectively ignored.
The reason for this is that HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY
is only evaluated by GsonHttpMessageConvertersConfiguration
, JacksonHttpMessageConvertersConfiguration
and JsonbHttpMessageConverterConfiguration
. There is however no such configuration class for kotlinx.serialization
.
Instead, AllEncompassingFormHttpMessageConverter
just adds kotlinx.serialization
to the standard HttpMessageConverters as provided by HttpMessageConvertersAutoConfiguration.messageConverters()
, and it happens to just get a higher priority than for instance Jackson when they are both in the classpath, regardless of whether the value spring.mvc.converters.preferred-json-mapper
property is set and regardless of what value it is set to.
To be more specific: bean provider method HttpMessageConvertersAutoConfiguration.messageConverters()
invokes one of the constructors of HttpMessageConverters
, which does not explicitly set the parameter addDefaultConverters
to false
, and it remains true
by default, which results in a call to the private helper method getCombinedConverters()
, which (through some other private methods) eventually defers to WebMvcConfigurationSupport.getMessageConverters()
, which calls the protected method addDefaultHttpMessageConverters()
, which (among others) adds AllEncompassingFormHttpMessageConverter
, which then autodetects a number of hard-coded serde frameworks in its init
block.
To reproduce, add the following to the application.yml
of a Spring Boot project:
spring:
mvc:
converters:
preferred-json-mapper: jackson
Then add both Jackson and kotlinx.serialization
dependencies to the project.
You will find that Spring will automatically pick KotlinSerializationJsonHttpMessageConverter
over MappingJackson2HttpMessageConverter
when (de)serializing JSON messages, even though Jackson was explicitly specified as the preferred JSON converter/mapper for the project.
Also, according to spring-configuration-metadata.json
, the property spring.mvc.converters.preferred-json-mapper
only supports the values gson
, jackson
and jsonb
. It doesn't have an option defined to explicitly select kotlinx.serialization
or any of the other serde frameworks that Spring autodetects in the static
block of the following classes:
- AllEncompassingFormHttpMessageConverter.java
- BaseDefaultCodecs.java
- DefaultRestClientBuilder.java
- RestTemplate.java
- WebMvcConfigurationSupport.java
This doesn't seem consistent, and it cost me quite a few hours to figure out why the addition of a non-spring library to my Spring project suddenly led to the project switching from Jackson to kotlinx.serialization
, without me changing anything to the Spring configuration.
In the meantime, I found that the following workaround worked for me to force Jackson to be used, even if kotlinx.serialization
was included in the classpath as a transitive dependency of another library:
import org.springframework.beans.factory.ObjectProvider
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter
import org.springframework.http.converter.HttpMessageConverter
@Configuration
class MessageConvertersConfig {
/**
* Bean provider that filters out standard [HttpMessageConverter]s that we don't want Spring to use, even when the
* serde frameworks for them are detected by Spring in the classpath.
*
* Should override [org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration.messageConverters]
*/
@Bean
fun filteredMessageConverters(converters: ObjectProvider<HttpMessageConverter<*>>): HttpMessageConverters =
HttpMessageConverters(
false,
converters.filter { converter ->
converter !is AbstractKotlinSerializationHttpMessageConverter<*>
},
)
}
But I'm reporting this as a bug, since this is not reasonably expected behavior, and it's not consistent (between the various supported serde frameworks) either.
Please let me know in this thread if you need any additional information.
Thank you for your time.