Issue
I wanted to use a configured version of Jackson ObjectMapper
in my project (ignoring null values and snake_case, also using some custom modules).
In my large project I wasn't able to get Spring MVC to actually use this mapper.
The build.gradle:
class="lang-groovy prettyprint-override">buildscript {
ext {
springBootVersion = '1.5.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile("org.springframework.boot:spring-boot-starter-jetty:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.8'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.8'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
My application.yml:
spring:
application:
name: Jackson test
jackson:
property-naming-strategy: SNAKE_CASE
default-property-inclusion: non_empty
debug: true
A container class:
public class MyLocationEntity {
public String nameAndSnake;
}
A config class:
@Configuration
@EnableWebMvc
public class AppConfig {
}
And a controller:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private ObjectMapper objectMapper;
@RequestMapping(value = "/test", produces = "application/json")
public MyLocationEntity test() throws JsonProcessingException {
MyLocationEntity location = new MyLocationEntity();
location.nameAndSnake = "hello world";
String expexted = objectMapper.writeValueAsString(location);
return location;
}
}
If I now look at the value of expected
in the debugger it is {"name_and_snake":"hello world"}
.
But if I let the controller run through the actual response is {"nameAndSnake":"hello world"}
.
When I remove @EnableWebMvc
it works. How can I use the configured mapper with MVC and not remove the rest of the autoconfiguration for Web MVC?
Solution
It's not evident just from the Javadocs, but @EnableWebMvc
disables the Spring Boot default web MVC auto configuration provided by WebMvcAutoConfiguration
, including the use of the Jackson ObjectMapper
bean configured by the application.yml
properties. Per the Spring Boot Reference Documentation:
9.4.7. Switch off the Default MVC Configuration
The easiest way to take complete control over MVC configuration is to provide your own
@Configuration
with the@EnableWebMvc
annotation. Doing so leaves all MVC configuration in your hands.
Since @EnableWebMvc
has the (likely surprising) behavior of disabling auto configuration, using this annotation may have unintended and undesirable side-effects. A different approach may be more appropriate.
That being said, it is possible that the behavior of @EnableWebMvc
is still desired. To use application.yml
properties in concert with the @EnableWebMvc
annotation, the MVC configuration must be manually configured to mimic the relevant disabled Spring Boot auto-configuration. There are a few different possible approaches to this.
The first approach is to duplicate the Spring Boot configuration code from WebMvcAutoConfiguration.EnableWebMvcConfiguration.configureMessageConverters()
. This will replace the message converters — including the MappingJackson2HttpMessageConverter
containing the unconfigured ObjectMapper
— with the ones that would have used with the default Spring Boot configuration:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ObjectProvider<HttpMessageConverters> messageConvertersProvider;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
}
Alternatively, rather than using the default Spring Boot list of message converters, it is possible to swap in just the ObjectMapper
or MappingJackson2HttpMessageConverter
bean provided by Spring Boot (which have the application.yml
properties applied):
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ObjectMapper objectMapper;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.map(c -> (MappingJackson2HttpMessageConverter) c)
.forEach(c -> c.setObjectMapper(objectMapper));
}
}
or
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.set(i, mappingJackson2HttpMessageConverter);
}
}
}
}
Answered By - M. Justin
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)