javaspring-bootjacksonspring-kafkaoffsetdatetime

How to make Spring Kafka JsonDeserializer retain the timezone offset when deserializing to OffsetDateTime


I have a message being received through Kafka, which I know has a non-UTC timezone in it. When I use the org.apache.kafka.common.serialization.StringDeserializer to verify this I get the right timestamp in ISO 8601 format with the timezone:

{  "id": "e499f2e8-a50e-4ff8-a9fe-0eaf9d3314bf", "sent_ts": "2021-02-04T14:06:10+01:00" }

When I switch to the org.springframework.kafka.support.serializer.JsonDeserializer this is lost. My POJO looks like this:

public class MyMessage {

    @JsonProperty("id")
    private String id;

    @JsonProperty("sent_ts")
    private OffsetDateTime sentTs;

    @Override
    public String toString() {
        return "MyMessage{" +
                "id='" + id + '\'' +
                ", sentTs=" + sentTs +
                '}';
}

When I log the message I receive I get:

MyMessage{id='e499f2e8-a50e-4ff8-a9fe-0eaf9d3314bf', sentTs=2021-02-04T13:06:10Z}

I thought the JsonDeserializer must be using Jackson so in my application.yml configuration I set:

spring.jackson:
    deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE: false

This didn't work. I also tried to a customizer:

@Configuration
public class ObjectMapperBuilderCustomizer implements Jackson2ObjectMapperBuilderCustomizer {

    @Override
    public void customize(Jackson2ObjectMapperBuilder builder) {
        builder.modules(new JavaTimeModule());
        builder.featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
    }
}

Which didn't work either.

I though maybe it needs to be a property of the Kafka consumer, so I also tried:

spring:
    consumer:
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE: false

Still doesn't work.

Is there a way to make the JsonDeserializer work properly and keep the correct timezone offset?


Solution

  • When you do like this value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer, the instance of that class is created by Apache Kafka client code which is fully not aware of Spring configuration.

    If you'd like to rely on the ObjectMapper configured by Spring Boot and your customizations, you should consider to do something like this:

    @Bean
    DefaultKafkaConsumerFactory kafkaConsumerFactory(KafkaProperties properties, ObjectMapper objectMapper) {
       Map<String, Object> consumerProperties = properties.buildConsumerProperties();
       JsonDeserializer<Object> jsonDeserializer = new JsonDeserializer<>(objectMapper);
       jsonDeserializer.configure(consumerProperties, false);
    
       return new DefaultKafkaConsumerFactory(consumerProperties, 
                       new StringDeserializer(), jsonDeserializer);
    }
    

    Pay attention how I call jsonDeserializer.configure(consumerProperties, false);. This way you still will be able to configure the rest of properties for Kafka consumer in the applicaiton.yml.

    Please, consider to raise GH issue for Spring Boot, so we will revise how we deal with JsonDeserializer and auto-configured ObjectMapper to server better end-user experience.