Spring HTTP Message Converters Customizing
A deep look at how Spring's HTTP Message Converters work.
A typical REST endpoint implemented using the Spring framework looks as follows:
@RestController public class SampleController { @GetMapping(path = "/data", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) public SampleData sampleData() { return new SampleData( new String[]{"sample1", "sample2"}); } }
Internally, Spring converts the SampleData
object using a HttpMessageConverter
.
Spring brings a set of default converters and you can customize your own one. For instance, when the SampleData
looks like this:
@Data @AllArgsConstructor public class SampleData { private String[] data; }Then, the default XML conversion ends up with the following structure:
<SampleData> <data> <data>sample1</data> <data>sample2</data> </data> </SampleData>But you may prefer a different structure:
<SampleData> <data>sample1</data> <data>sample2</data> </SampleData>
To achieve this with Spring Boot (you need spring-boot-starter-web
dependency and @EnableAutoConfiguration
annotation on your application configuration), you have just to register a converter bean:
@Bean public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() { return new MappingJackson2XmlHttpMessageConverter( new Jackson2ObjectMapperBuilder() .defaultUseWrapper(false) .createXmlMapper(true) .build() ); }
We could end here as things are so easy with Spring Boot — the converter is registered and used in the needed order automatically.
As a note, it is worth mentioning another option to customize message converters with Spring Boot — a WebMvcConfigurer
configuration. The interface comes actually from Spring MVC, but its usage from within a Spring Boot application has some specifics:
@Configuration class Config implements WebMvcConfigurer { @Override public void configureMessageConverters( List<HttpMessageConverter<?>> converters) { converters.add(0, new MappingJackson2XmlHttpMessageConverter( new Jackson2ObjectMapperBuilder() .defaultUseWrapper(false) .createXmlMapper(true) .build() )); } }
Spring Boot web auto-configuration (concretely WebMvcAutoConfiguration
) brings a default configurer which means we can register as many custom configurers we want to without danger to overwrite the default configuration.
That's why we can overwrite the configureMessageConvertersmethod
without losing the default message converters, but we have to put it at the beginning of the list to take over from the already included defaults (by converting the first applicable converter will be used).
Spring Boot Aside
If we can't use Spring Boot and must stick with Spring framework only, just registering a converter bean is no more enough.
One option is to use your own WebMvcConfigurer
as we already seen above. But the situation without Spring Boot is slightly different.
Spring Web MVC default web configuration (enabled by annotating a configuration with @EnableWebMvc
) collects instances of WebMvcConfigurer
into a composite where all the converters live in a list.
That means, overwriting the configureMessageConverters
method would remove the default converters as well which is, usually, undesirable.
In this case, the method override will do the job. Again, you have to put the custom converter at the beginning of the list:
@EnableWebMvc @Configuration class MessageConvertersConfig implements WebMvcConfigurer { @Override public void extendMessageConverters( List<HttpMessageConverter<?>> converters) { converters.add(0, new MappingJackson2XmlHttpMessageConverter( new Jackson2ObjectMapperBuilder() .defaultUseWrapper(false) .createXmlMapper(true) .build() )); } }
Another way is to overwrite the entire configuration. This assumes removing @EnableWebMvc
from your codebase. The code is similar to the previous one:
@Configuration class WebConfig extends WebMvcConfigurationSupport { @Override protected void extendMessageConverters( List<HttpMessageConverter<?>> converters) { converters.add(0, new MappingJackson2XmlHttpMessageConverter( new Jackson2ObjectMapperBuilder() .defaultUseWrapper(false) .createXmlMapper(true) .build() )); } }
As you can see, there is more to be aware of when working without the convenience of using Spring Boot, but it’s still pretty straightforward.
And that’s it. I hope this helped a bit.
You can find working examples on my GitHub.
Happy converting!