Duplicate MessageConverters in RestTemplateBuilder?

1.8k Views Asked by At

I'm using spring-boot with spring-web and jackson.

Problem: when a RestTemplate is initialized automatically by spring, the constructor receives some duplicate MessageConverters:

org.springframework.http.converter.ByteArrayHttpMessageConverter@6a1b4854,
org.springframework.http.converter.StringHttpMessageConverter@2d5b549b, 
org.springframework.http.converter.StringHttpMessageConverter@6a175162, 
org.springframework.http.converter.ResourceHttpMessageConverter@7641c4e7, 
org.springframework.http.converter.ResourceRegionHttpMessageConverter@650a0b50, 
org.springframework.http.converter.xml.SourceHttpMessageConverter@55e3b64d, 
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@52f71d2, 
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@f3c27e9, 
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7d31fb6c, 
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@701c413, 
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@48543f11

You see, there are 3 duplicates:

StringHttpMessageConverter
MappingJackson2HttpMessageConverter
MappingJackson2XmlHttpMessageConverter

As I don't initialize any message converters myself: why does the application context contain duplicate converters at all, that are then added to the resttemplate?

Especially: doesn't this confuse the (de)serializing if some converters occur duplicate (but with different configuration)?

For example: the ObjectMapper of the first MappingJackson2HttpMessageConverter contains more registeredModuleTypes [Jdk8Module, JavaTimeModule, ParamterNamesModule, JsonComponentModule, GeoModule] than the 2nd one (that only contains: [Jdk8Module, JavaTimeModule]).

Does that make sense?

It's instantiated via RestTemplateAutoConfiguration.restTemplateBuilder(), there all the duplicate MessageConverters are already present.

1

There are 1 best solutions below

2
LppEdd On BEST ANSWER

The culprit is here, at HttpMessageConverters

public HttpMessageConverters(boolean addDefaultConverters,
        Collection<HttpMessageConverter<?>> converters) {
    List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
            addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
    combined = postProcessConverters(combined);
    this.converters = Collections.unmodifiableList(combined);
}

Specifically, this line (formatted)

List<HttpMessageConverter<?>> combined = 
       getCombinedConverters(
           converters, 
           addDefaultConverters 
               ? getDefaultConverters() 
               : Collections.emptyList());

The converters collection contains the scanned HttpMessageConverter(s).
Based on the environment.

enter image description here

That list is then joined with a default one provided by WebMvcConfigurationSupport

enter image description here

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    private static final boolean romePresent;
    private static final boolean jaxb2Present;
    private static final boolean jackson2Present;
    private static final boolean jackson2XmlPresent;
    private static final boolean jackson2SmilePresent;
    private static final boolean jackson2CborPresent;
    private static final boolean gsonPresent;
    private static final boolean jsonbPresent;
    ...

Infact the documentations for WebMvcConfigurationSupport states

This class registers ... ... a range of HttpMessageConverters depending on the third-party libraries available on the classpath.

The scanned HttpMessageConverter(s) are found and instantiated via HttpMessageConvertersAutoConfiguration, whose documentation is

Auto-configuration for HttpMessageConverters.

That class exposes by itself a StringHttpMessageConverter

@Bean
@ConditionalOnMissingBean
public StringHttpMessageConverter stringHttpMessageConverter() {
    StringHttpMessageConverter converter = new StringHttpMessageConverter(
            this.properties.getCharset());
    converter.setWriteAcceptCharset(false);
    return converter;
}

Than, it imports Jackson or Gson auto-configurations

@Import({ 
    JacksonHttpMessageConvertersConfiguration.class
    GsonHttpMessageConvertersConfiguration.class,
    JsonbHttpMessageConvertersConfiguration.class 
})

And that's how those environment-based ones are "summed" to the pre-defined ones.


Spring doesn't get confused by duplicates because it just takes the first which is compatible.
See how an HttpMessageConverter is choosed

enter image description here

You can see it is just a simple for loop, and each convert is asked to say "can I do this?" via the canWrite method

enter image description here

The first valid is picked.