Serialisation into SearchResponse takes a lot of time

I am using Elastic Search Java client to search for documents in elasticsearch.

The search request itself on ES takes (took) 165ms, however the ovreall time taken to convert the same into SearchResponse takes about 1217ms.

I added an HTTPResponseIntercepter and HTTPRequestInterceptor that would account for the network delay, which denotes the time taken as 168ms.

So the gist of it looks like the serialisation of the search response into SearchResponse itself takes 1217 - 168 = 1049ms, which seems to be quite high.

Why does this step take 1049ms? Is there a way to optimise this number?

The logs:

ransactionId:[00000000-0000-0000-0000-000000000000] [2025-09-19T06:12:33.584-0400 [ForkJoinPool-1-worker-43] DefaultElasticSearchRepository.search():561 INFO ]: πŸ”Œ [ES-CONNECTION] About to call elasticsearchClient.search() - Time: 1758276753584
TransactionId:[00000000-0000-0000-0000-000000000000] [2025-09-19T06:12:33.586-0400 [ForkJoinPool-1-worker-43] ElasticsearchConnectionInterceptor.process():44 INFO ]: πŸ”Œ [ES-CONNECTION] HTTP request interceptor CALLED - URI: /opt/_search?typed_keys=true, Method: POST, Time: 1758276753586
TransactionId:[00000000-0000-0000-0000-000000000000] [2025-09-19T06:12:33.754-0400 [elasticsearch-rest-client-0-thread-2] ElasticsearchConnectionInterceptor.process():56 INFO ]: πŸ”Œ [ES-CONNECTION] HTTP response interceptor CALLED - Status: 200, Total Time: 168ms
TransactionId:[00000000-0000-0000-0000-000000000000] [2025-09-19T06:12:33.890-0400 [ForkJoinPool-1-worker-43] RestClient.logResponse():58 DEBUG]: request [POST http://u-batchqa-batch-elasticsearch.sdlb.deshaw.com/opt/_search?typed_keys=true] returned [HTTP/1.1 200 OK]
TransactionId:[00000000-0000-0000-0000-000000000000] [2025-09-19T06:12:34.801-0400 [ForkJoinPool-1-worker-43] DefaultElasticSearchRepository.search():576 INFO ]: βœ… [SEARCH-FLOW] DefaultElasticSearchRepository.search completed - Total Time: 1217ms, Response Processing: 0ms, Hits: 2000, ES took: 165ms

The code snippet:


import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;

public SearchResponse<T> search(SearchRequest searchRequest, Class<T> type) throws BatchSearchException {
        long repositoryStartTime = System.currentTimeMillis();
        log.info("πŸ” [SEARCH-FLOW] DefaultElasticSearchRepository.search started - Index: '{}', Type: {}", 
                searchRequest.index(), type.getSimpleName());
        
        try {

            log.info("πŸ”Œ [ES-CONNECTION] About to call elasticsearchClient.search() - Time: {}", repositoryStartTime);

            SearchResponse<T> response = elasticsearchClient.search(searchRequest, type);
            
            // Step 2: Process the response
            long responseProcessingStartTime = System.currentTimeMillis();
            int hitCount = response.hits().hits().size();
            Long esTook = response.took();
            long responseProcessingEndTime = System.currentTimeMillis();

            long repositoryEndTime = System.currentTimeMillis();
            
            // Log detailed timing breakdown
            log.info("βœ… [SEARCH-FLOW] DefaultElasticSearchRepository.search completed - Total Time: {}ms, Response Processing: {}ms, Hits: {}, ES took: {}ms",
                    repositoryEndTime - repositoryStartTime, 
                    responseProcessingEndTime - responseProcessingStartTime,
                    hitCount, esTook);
            return response;
        } catch (ElasticsearchException e) {
            String failureMessage = String.format("Unable to search objects with query %s in index %s in Elasticsearch due to error: %s",
                    searchRequest, searchRequest.index(), e.getMessage()
            );
            log.error(failureMessage);
        } catch (IOException e) {
            String failureMessage = String.format(
                    "Unable to establish connection with Elasticsearch server while searching query %s in index %s due to" +
                            " error: %s", searchRequest, searchRequest.index(), e.getMessage()
            );
            log.error(failureMessage);
        }
    }

ElasticSearchClient:

@Bean
    public ElasticsearchClient elasticsearchClient(OpenTelemetry openTelemetry) {
        
        URI uri = URI.create(elasticsearchServiceUrl);
        
        ElasticsearchConnectionInterceptor connectionInterceptor = new ElasticsearchConnectionInterceptor();

        RestClient restClient = RestClient
                .builder(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()))
                .setHttpClientConfigCallback(httpClientBuilder ->
                        httpClientBuilder
                                .addInterceptorFirst((HttpRequestInterceptor) connectionInterceptor)
                                .addInterceptorLast((HttpResponseInterceptor) connectionInterceptor)
                )
                .build();

        transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

        ElasticsearchClient client = new ElasticsearchClient(transport);
        
        return client;
    }

ElasticSearchConnectionInterceptor:

@Slf4j
public class ElasticsearchConnectionInterceptor implements HttpRequestInterceptor, HttpResponseInterceptor {

    private static final String REQUEST_START_TIME = "es.request.start.time";

    @Override
    public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
        long requestStartTime = System.currentTimeMillis();
        context.setAttribute(REQUEST_START_TIME, requestStartTime);
        
        String uri = request.getRequestLine().getUri();
        String method = request.getRequestLine().getMethod();
        
        log.info("πŸ”Œ [ES-CONNECTION] HTTP request interceptor CALLED - URI: {}, Method: {}, Time: {}", 
                uri, method, requestStartTime);
        
    }

    @Override
    public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
        Long requestStartTime = (Long) context.getAttribute(REQUEST_START_TIME);
        if (requestStartTime != null) {
            long requestEndTime = System.currentTimeMillis();
            long totalTime = requestEndTime - requestStartTime;
            
            log.info("πŸ”Œ [ES-CONNECTION] HTTP response interceptor CALLED - Status: {}, Total Time: {}ms", 
                    response.getStatusLine().getStatusCode(), totalTime);
           
        } else {
            log.warn("πŸ”Œ [ES-CONNECTION] HTTP response interceptor CALLED but no start time found in context");
        }
    }
}

The same Search request when hit via the browser (The browser is in Hyderabad India, and the ES is in NYC. However, that’s not the case with the Java application, the application and the ES runs in NYC itself. ):

Client dependency:

 <dependency>
     <groupId>co.elastic.clients</groupId>
     <artifactId>elasticsearch-java</artifactId>
     <version>8.14.3</version>      
</dependency>

Hey

What is the searchRequest looking like?
Also, could you upgrade your client to 8.19.4?

This is called when receiving the start of the HTTP response, so you are not accounting for the time it takes to download the response body.

Also JSON parsing is not free, it can be quite allocation-heavy (particularly if parsing 2000 hits from the response).

You probably need to attach a profiler to see exactly where the time is being spent, but I would imagine it’s one of these two things.