4

I am trying to configure my ObjectMapper to have a specific TimeZone instead of using the Default GMT. I followed the instructions from different places (see references below) trying to configure via code and by XML. After debugging I notice that the code does set the ObjectMapper's TimeZone setting once, however during start up the Jackson2ObjectMapperBuilder class is called multiple times and overrides (null'ing) my TimeZone setting and returns to default GMT by the completion of the startup process, both in Tomcat and in my unit test.

Some code:

Config:

@Configuration
@EnableWebMvc
public class JacksonMapperConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(converter());
    }

    @Bean    
    public MappingJackson2HttpMessageConverter converter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jacksonObjectMapper());
        return converter;
    }

    @Bean
    public ObjectMapper jacksonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        return objectMapper;
    }

    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.timeZone("America/New_York");
        return builder;
    }
}

Entity:

@Entity
public class AllocationEstimate extends AbstractEntity<Integer> {

    private static final long serialVersionUID = 1L;

    @NotNull(message = "validation.mandatoryField")
    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    @JsonFormat(pattern = "MM/dd/yyyy")
    private Date endDate;

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }
}

Controller:

@RestController
@RequestMapping("/allocation-estimate")
public class AllocationEstimateRestController extends AbstractGenericCrudRestController<AllocationEstimate, Integer> {

    private final AllocationEstimateService allocationEstimateService;

    @Autowired
    public AllocationEstimateRestController(AllocationEstimateService allocationEstimateService) {
        this.AllocationEstimateService = allocationEstimateService;
    }

    @Override
    @Post
    public AllocationEstimate createOrUpdate(@RequestBody @Valid AllocationEstimate resource) {
        return super.createOrUpdate(resource);
    }
}

I have used all of the above and a combination of different settings. Some with only configureMessageConverters, or only trying to set a @Bean.

My question is if anyone has seen this behavior and how can I fix it? I've tried a combination of different things but my settings are always overridden by the end of the bootup process. I am using Spring 4.3.16.

For Reference:

Configuring ObjectMapper in Spring

https://dzone.com/articles/latest-jackson-integration

https://www.javacodegeeks.com/2014/09/customizing-httpmessageconverters-with-spring-boot-and-spring-mvc.html

How to force Jackson2ObjectMapperBuilder in spring-boot?

SpringMVC Jackson2HttpMessageConverter customization doesn't work

EDIT I'm suspecting that AllEncompassingFormHttpMessageConverter is the culprit, I am not sure what it is but something is instantiating this class multiple times which in turn instantiates MappingJackson2HttpMessageConverter multiple times and thus overriding my converter that I have in my config.

Joe A
  • 300
  • 3
  • 9

2 Answers2

1

Add @Primary to your jacksonObjectMapper() and jacksonBuilder() beans.

I think Spring try to initialized some hidden ObjectMapper pulled in by different jars and the primary one is not yours.

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
  • Thank you for your response. I have actually tried that as well. I even marked all the methods @Primary but specifically the two you mentioned. The Builder class still is still called without the TimeZone property set. – Joe A May 15 '18 at 14:39
  • Please show the configuration related code and how you autowire `jacksonObjectMapper` and `jacksonBuilder` bean. _Jackson2ObjectMapperBuilder class is called multiple times_ this part is very unusual since it's supposed to call only once – Mạnh Quyết Nguyễn May 15 '18 at 14:57
  • Our configuration is via XML. Is there anything specific you'd like to see? The mapper and builder are not autowired, we simply annotate the Date field with JsonFormat and the RestController handles the conversion. – Joe A May 15 '18 at 15:11
  • It would be great if you post those XML. Since you annotated your class with annotation, I don't know why you still need XML for configuration – Mạnh Quyết Nguyễn May 15 '18 at 15:12
  • Sorry, I confused myself. There is no XML related configuration to this specifically. I followed the instructions from the links I posted, so I don't believe there is anything missing. – Joe A May 15 '18 at 15:26
  • Could you share a minimal project of yours? I want to debug on my local – Mạnh Quyết Nguyễn May 15 '18 at 15:29
  • I added code, I'd have to create a new project and put this together to be able to share anything. Adding break points to the classes Jackson2ObjectMapperFactoryBean, MappingJackson2HttpMessageConverter, and Jackson2ObjectMapperBuilder should show you this behavior with Spring 4.3.16 and jackson 2.9.4. – Joe A May 15 '18 at 17:33
  • My computer just sleep cause of battery. I'll get back to you by tomorrow – Mạnh Quyết Nguyễn May 15 '18 at 17:37
  • I don't believe @Primary would do the trick. `Jackson2ObjectMapperBuilder` is instensitated in `MappingJackson2HttpMessageConverter` in static way, not through Spring `ApplicationContext` – Hank Dec 27 '19 at 11:12
0

I solved this problem by configuring ObjectMapper bean directory after it's instantiated by Spring Boot.

    @Configuration
    @AutoConfigureAfter(JacksonAutoConfiguration.class)
    public static class RegistryJsonConfigurer implements InitializingBean {

        @Autowired
        private ObjectMapper objectMapper;

        @Override
        public void afterPropertiesSet() {
            SimpleModule module = new SimpleModule();
            module.addDeserializer(Something.class, new CustomDeserializer<>(Something.class));
            objectMapper.registerModule(module);
        }
    }
Hank
  • 1,318
  • 10
  • 29