Skip to content

Provide auto-configuration for kotlinx.serialization and KotlinSerializationJsonHttpMessageConverter #39853

Closed as not planned
@volkert-fastned

Description

@volkert-fastned

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions