Search code examples
javaspringelasticsearchspring-data

How to make Spring Data Elasticsearch work with java.time.LocalDateTime for date


I am using Spring Data support for Elasticsearch. Here is the timestamp field mapping:

@Field(type = FieldType.Date, index = FieldIndex.not_analyzed, store = true,
        format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
private LocalDateTime timestamp;

This results in mapping of the field in Elasticsearch as follows:

"timestamp":{"type":"date","store":true,"format":"yyyy-MM-dd'T'HH:mm:ss.SSSZZ"}

When I use java.util.Date instead everything works fine. However, when I switch to java.time.LocalDateTime as above the document sent to Elasticsearch causes an exception. Here is the document (timestamp field only for brevity):

"timestamp": {
    "hour":7, "minute":56, "second":9, "nano":147000000, "year":2017, "month":"FEBRUARY",
    "dayOfMonth":13, "dayOfWeek":"MONDAY", "dayOfYear":44, "monthValue":2, "chronology": {
        "id":"ISO", "calendarType": "iso8601"
    }
}

And the exception:

MapperParsingException[failed to parse [timestamp]]; nested: IllegalArgumentException[unknown property [hour]];
(...)
Caused by: java.lang.IllegalArgumentException: unknown property [hour]

It looks like the pattern is being ignored here when jsonizing the document. Any possible tips? Or perhaps you might know how to use the "built-in" _timestamp field with Spring Data?


Solution

  • Check https://github.com/spring-projects/spring-data-elasticsearch/wiki/Custom-ObjectMapper to add JavaTimeModule to your ObjectMapper.

    @Configuration
    public class ElasticSearchConfiguration {
    
      @Bean
      public ElasticsearchTemplate elasticsearchTemplate(Client client) {
        return new ElasticsearchTemplate(client, new CustomEntityMapper());
      }
    
      public static class CustomEntityMapper implements EntityMapper {
    
        private final ObjectMapper objectMapper;
    
        public CustomEntityMapper() {
          objectMapper = new ObjectMapper();
          objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
          objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
          objectMapper.registerModule(new CustomGeoModule());
          objectMapper.registerModule(new JavaTimeModule());
        }
    
        @Override
        public String mapToString(Object object) throws IOException {
          return objectMapper.writeValueAsString(object);
        }
    
        @Override
        public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
          return objectMapper.readValue(source, clazz);
        }
      }
    }