Search code examples
spring-bootelasticsearchelasticsearch-java-api-client

Elasticsearch Java API Client TransportException [es/search] Failed to decode response


I am trying to set up the new Elasticsearch Java API Client. Here is my configuration:

    // Set up elasticsearch cluster hosts
    String[] elasticsearchHosts = elasticsearchEndpoints.split(",");
    HttpHost[] httpHosts = Arrays.stream(elasticsearchHosts)
            .map(HttpHost::create).toArray(HttpHost[]::new);

    // Create the low level client
    final RestClientBuilder restClientbuilder = RestClient.builder(httpHosts);

    // Set elasticsearch user credentials
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    credentialsProvider.setCredentials(AuthScope.ANY,
            new UsernamePasswordCredentials(username, password));

    restClientbuilder.setHttpClientConfigCallback(
            b -> b.setDefaultCredentialsProvider(credentialsProvider));

    RestClient restClient = restClientbuilder.build();

    // Create the transport with a Jackson mapper which maps classes to json
    ElasticsearchTransport transport = new RestClientTransport(restClient,
            new JacksonJsonpMapper());

    return new ElasticsearchClient(transport);

and I have a simple search:

  Query termQuery = TermQuery.of(m -> m
                    .field("myField")
                    .value("myValue"))
            ._toQuery();

    var searchRequest =
            co.elastic.clients.elasticsearch.core.SearchRequest.of(s -> s
                    .index("index-name")
                    .query(termQuery));

    SearchResponse<myDTOClass>
            searchResponse = elasticsearchJavaApiClient.search(searchRequest, myDTOClass.class);

It sends the request successfully but the problem is the response. Here is the relevant stack trace error:

co.elastic.clients.transport.TransportException: node: https://myDomain:9200/, status: 200, [es/search] Failed to decode response
...

Caused by: co.elastic.clients.json.JsonpMappingException: Error deserializing co.elastic.clients.elasticsearch.core.search.Hit: jakarta.json.JsonException: Jackson exception (JSON path: hits.hits[0]._source) (line no=1, column no=336, offset=-1)
    at co.elastic.clients.json.JsonpMappingException.from0(JsonpMappingException.java:134)
    at co.elastic.clients.json.JsonpMappingException.from(JsonpMappingException.java:121)
    at co.elastic.clients.json.ObjectDeserializer.deserialize(ObjectDeserializer.java:218)
    at co.elastic.clients.json.ObjectDeserializer.deserialize(ObjectDeserializer.java:148)
...
Caused by: jakarta.json.JsonException: Jackson exception
    at co.elastic.clients.json.jackson.JacksonUtils.convertException(JacksonUtils.java:39)
    at co.elastic.clients.json.jackson.JacksonJsonpMapper$JacksonValueParser.deserialize(JacksonJsonpMapper.java:142)
...
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.OffsetDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 335] (through reference chain: com.mygw.dto.MyModel["MyField"])

Solution

  • SOLUTION: I found the reason and wanted to share with you.

    The reason is that I used OffsetDateTime in my myDTOClass.class but Jackson library does not parse Java 8 Time classes. So you need to add the module to your configuration above.

    Relevant part:

    ...
    RestClient restClient = restClientbuilder.build();
    
            ObjectMapper mapper = JsonMapper.builder()
                    .addModule(new JavaTimeModule())
                    .build();
    
            // Create the transport with a Jackson mapper which maps classes to json
            ElasticsearchTransport transport = new RestClientTransport(restClient,
                    new JacksonJsonpMapper(mapper));
    ...
    

    And of course you need to add lombok annotations to myDTOClass.class

    @Getter
    @ToString
    @NoArgsConstructor
    

    WORKAROUND: Instead of using myDTOClass.class, you can use Void.class to test the connection and ObjectNode.class to get the response in raw json.

    SearchResponse<myDTOClass> searchResponse = elasticsearchJavaApiClient.search(searchRequest, myDTOClass.class);
    

    Here is the documentation: https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/reading.html

    In the end this was the solution for me:

    SearchResponse<ObjectNode> searchResponse = elasticsearchJavaApiClient.search(searchRequest, ObjectNode.class);
    

    Of course I had to convert the raw json object into my own myDTOClass.