When upgrading from Spring Boot version 2.1.13
to 2.2.6
my Jackson object mapper is no longer automatically registered as the object mapper for Spring REST requests.
For example, the following class will no longer use the customized object mapper for REST requests:
@SpringBootApplication
class BadObjectMapperApplication {
companion object {
val objectMapper = ObjectMapper()
.apply {
findAndRegisterModules()
val dateSer = object : StdSerializer<LocalDate>(LocalDate::class.java) {
override fun serialize(value: LocalDate, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE))
}
}
val dateDeser = object : StdDeserializer<LocalDate>(LocalDate::class.java) {
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): LocalDate =
LocalDate.parse(jp.readValueAs(String::class.java))
}
registerModule(JavaTimeModule()
.addSerializer(dateSer)
.addDeserializer(LocalDate::class.java, dateDeser)
)
}
}
@Bean
fun objectMapper() = objectMapper
}
I've included a zip Spring Boot project you can run to see the problem. Just access http://localhost:8080
to see the issue. You can swith the spring boot version to 2.1.13
to see the LocalDate
serializer actually work with older versions of Spring Boot.
bad-object-mapper.zip
Comment From: joca-bt
Have you tried customizing your object mapper via
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { ... }
inside a configuration class? We've configured our object mapper this way in 2.2.5 and works fine.
Comment From: CorayThan
Have you tried customizing your object mapper via
@Bean public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { ... }
inside a configuration class? We've configured our object mapper this way in 2.2.5 and works fine.
Is this documented anywhere? I tried googling for how to configure the object mapper, but I didn't find this anywhere. Added it to the top stackoverflow question tho. Hopefully helps the next person.
Comment From: joca-bt
https://github.com/spring-projects/spring-boot/issues/18449
Comment From: wilkinsona
@CorayThan Thanks for the sample.
There's nothing wrong with the approach that you have taken if you want to take complete control over the ObjectMapper
. The Jackson2ObjectMapperBuilder
approach allows you to tune the auto-configured ObjectMapper
.
The reason that you are seeing a difference in behaviour with Spring Boot 2.1.x versus Spring Boot 2.2.x is due to the underlying version of Jackson that's used. Spring Boot 2.1.x uses Jackson 2.9 and Spring Boot 2.2.x uses Jackson 2.10. Calling findAndRegisterModules()
with the former registers com.fasterxml.jackson.datatype.jsr310.JSR310Module
whereas with the latter it registers com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
. When JavaTimeModule
is already registered your call to registerModule()
has no effect.
You can get the behaviour you want with Jackson 2.10 by registering your customized JavaTimeModule
before calling findAndRegisterModules()
:
val dateSer = object : StdSerializer<LocalDate>(LocalDate::class.java) {
override fun serialize(value: LocalDate, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE))
}
}
val dateDeser = object : StdDeserializer<LocalDate>(LocalDate::class.java) {
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): LocalDate =
LocalDate.parse(jp.readValueAs(String::class.java))
}
registerModule(JavaTimeModule()
.addSerializer(dateSer)
.addDeserializer(LocalDate::class.java, dateDeser)
)
findAndRegisterModules()