Issue
In a Spring Boot/Spring Cloud Stream application, we're using the Kafka binder. I've noticed that when deserializing Kafka messages that include a ZonedDateTime
property, Jackson is automatically changing the time zone (to UTC) even when the serialized form includes time zone (eg, 20210101084239+0200
).
After some research, I discovered this is a default deserialization feature that can be disabled, so I added the following in one of my @Configuration
classes:
@Bean
public Jackson2ObjectMapperBuilderCustomizer objectMapperCustomizer() {
return builder -> {
builder.featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
//... other custom config...
};
But that doesn't work, presumably because the ObjectMapper that Spring Cloud Stream (or the Kafka binder) uses is not constructed from the Jackson2ObjectMapperBuilder
that's configured in the application context :-(.
After some more research, I found someone claiming that it was necessary to override Spring Messaging's MappingJackson2MessageConverter
since it creates its own ObjectMapper
. So I added this:
@Bean
public MappingJackson2MessageConverter springMessagingJacksonConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.getObjectMapper().configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
return converter;
}
Unfortunately, that still didn't work. I still am getting ZonedDateTime
objects deserialized ignoring the time zone info in the JSON values.
So what bean creates the ObjectMapper
used by Spring Cloud Stream Kafka binder, and how can I customize its configuration?
Solution
As documented in discussion comments below and here, there is a bug in Spring Cloud Stream that it's not using the ObjectMapper
from the Spring application context in places that it should.
Having said that, a workaround is to define your own Serde<>
@Bean which calls the JsonSerde<>
constructor that takes an ObjectMapper
, which is injected from the app context. For example:
@Configuration
public class MyConfig {
@Bean
public Serde<SomeClass> someClassSerde(ObjectMapper jsonMapper) {
return new JsonSerde<SomeClass>(SomeClass.class, jsonMapper);
}
}
The downside to this workaround is that you have to do it for every target type that you want to serialize/deserialize in messages.
Answered By - E-Riz