5

Spring Boot defaults allow both dates and datetime values in LocalDate.

E.g. for following DTO:

public class Person {

  private LocalDate birthDate;

  //getter & setters
}

Both of the following are acceptable:

{
  "birthDate" : "2021-07-24"
}
{
  "birthDate" : "2021-07-24T17:00:00Z"
}

Jackson has a lenient flag that can be set to reject dates with time:

@JsonFormat(pattern="uuuu-MM-dd", lenient = OptBoolean.FALSE)
private LocalDate birthDate;

Is there a corresponding Spring Boot application properties flag that can be set to apply globally to all LocalDate fields, so that the annotation does not need to be added to every LocalDate field and without needing to define a custom deserializer like this?

M. Justin
  • 14,487
  • 7
  • 91
  • 130
SGB
  • 2,118
  • 6
  • 28
  • 35
  • I've tested this myself locally, and the the `lenient` flag isn't what is causing the rejection of the date/time values. Rather, it's the presence of the `pattern` flag that appears to be doing that. – M. Justin Jul 26 '21 at 20:12
  • It looks like the not-yet-released Jackson 2.13 will also allow the `lenient` flag to cause rejection of dates with times: https://github.com/FasterXML/jackson-modules-java8/issues/212. – M. Justin Aug 02 '21 at 05:59

2 Answers2

6

Before addressing the question of setting the lenient flag globally, it's worth noting that setting this flag in this particular example (which was written in a version of Jackson prior to 2.13.0) is not what actually achieved the desired result. Rather, it is the presence of the pattern annotation element that caused the value to be rejected, not the lenient annotation element. The following would therefore be sufficient to reject the example datetime value:

@JsonFormat(pattern="uuuu-MM-dd")
private LocalDate birthDate;

Regarding the question of setting the lenient flag globally in a Spring Boot application, the answer to this question depends on the version of Spring Boot and Jackson being used, as newer versions of these libraries have added functionality around this behavior.

Spring Boot 2.6.0 and Jackson 2.13.0

Using Spring Boot 2.6.0 and Jackson 2.13.0, the spring.jackson.default-leniency application property can be set to false to achieve the desired result.

spring.jackson.default-leniency: false

Starting with Jackson 2.13.0, setting the lenient flag to false causes causes dates with times to be rejected when the underlying type is a date without a time (jackson-modules-java8#212).

Spring Boot 2.6.0 added the spring.jackson.default-leniency flag to configure the default leniency on the Spring Boot provided ObjectMapper bean (spring-boot#27547).

Therefore, combining these two library versions, and utilizing the spring.jackson.default-leniency flag achieves the desired result of rejecting dates with time components for LocalDate values.

Spring Boot 2.5.x (or earlier) and Jackson 2.13.0

Spring Boot 2.5.x does not have an application property to set the default leniency of the Spring Boot provided ObjectMapper bean. However, it is possible to customize this bean in other ways to set the default leniency. As noted above, this will achieve the desired result of rejecting dates with times in Jackson 2.13.x or later.

The most straightforward approach to setting the default leniency in a Spring Boot 2.5.x application is probably to use a custom Jackson2ObjectMapperBuilderCustomizer bean to configure the ObjectMapper:

@Configuration
public class CustomizedJacksonConfiguration {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer nonLenientObjectMapperBuilderCustomizer() {
        return builder -> builder.postConfigurer(
                objectMapper -> objectMapper.setDefaultLeniency(false));
    }
}

Jackson 2.12.x (or earlier)

As noted above, setting the lenient flag to false does not actually cause Jackson versions prior to 2.13 to reject values with a time part.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
1

I do not know if there is such a property but you can achieve this by configuring your ObjectMapper. Configuration class (like here) needs few lines of code but it is good to have in case there is something else also to configure globally.

Have a look on this configuration class:

@Configuration
public class JacksonConfiguration {
    public static final String LOCAL_DATE_PATTERN = "yyyy-MM-dd";

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter
                        .ofPattern(LOCAL_DATE_PATTERN)));
        mapper.registerModule(timeModule);
        return mapper;
    }
}

Jackson uses JavaTimeModule to parse LocalDate (and LocalDateTime). JavaTimeModule includes default deserializers and there seems not to be a direct way to access them so the trick is to replace the one that deserializes LocalDate. Setting the pattern explicitly to one in example will cause parse error when there is time also within given LocalDate(Time).

As you see there seems to be a need for a custom deserializer but it is not a big deal to set it on a configuration class.

pirho
  • 11,565
  • 12
  • 43
  • 70