Elasticsearch RestClient Queries

📢 This article was translated by gemini-2.5-flash

Elasticsearch Series

ContentLink
Elasticsearch Basic Operationshttps://blog.yexca.net/archives/226
Elasticsearch Query Operationshttps://blog.yexca.net/archives/227
RestClient Basic Operationshttps://blog.yexca.net/archives/228
RestClient Query OperationsThis Article
Elasticsearch Data Aggregationhttps://blog.yexca.net/archives/231
Elasticsearch Autocompletehttps://blog.yexca.net/archives/232
Elasticsearch Data Synchttps://blog.yexca.net/archives/234
Elasticsearch Clusterhttps://blog.yexca.net/archives/235

Querying documents also uses the RestHighLevelClient object.

match_all

Here’s how to initiate the request:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
public void testMatchAll() throws IOException {
    // Prep the request
    SearchRequest request = new SearchRequest("hotel");
    // Build DSL parameters
    request.source().query(QueryBuilders.matchAllQuery());
    // Send the request
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    System.out.println(response);
}

Parsing the Response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testMatchAll() throws IOException {
    // Prep the request
    SearchRequest request = new SearchRequest("hotel");
    // Build DSL parameters
    request.source().query(QueryBuilders.matchAllQuery());
    // Send the request
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    // Parse the results
    SearchHits searchHits = response.getHits();
    // Total hits
    long total = searchHits.getTotalHits().value;
    // Array of search results
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        String json = hit.getSourceAsString();
        System.out.println(json);
    }
}

ES returns a JSON string, which includes:

  • hits: Matching results
    • total: Total hits, where value is the exact count.
    • max_score: Relevance score for the highest-scoring document among all results.
    • hits: Array of search result documents, each a JSON object.
      • _source: Original data in the document, also a JSON object.

So, parsing the response means parsing the JSON string layer by layer, as follows:

  • SearchHits: Obtained via response.getHits(), this is the outermost hits in the JSON, representing the matched results.
    • SearchHits.getTotalHits().value: Gets the total hit count.
    • SearchHits.getHits(): Gets the SearchHit array, which is the document array.
      • SearchHit.getSourceAsString(): Gets _source from the document results, which is the raw JSON document data.

match and multi_match

Similar to match_all, the difference is the query condition.

match Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
public void testMatch() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().query(QueryBuilders.matchQuery("all", "如家"));
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    SearchHits searchHits = response.getHits();
    long total = searchHits.getTotalHits().value;
    System.out.println(total);
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        String json = hit.getSourceAsString();
        System.out.println(json);
    }
}

multi_match Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
public void testMultiMatch() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().query(QueryBuilders.multiMatchQuery("如家", "brand", "name"));
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    SearchHits searchHits = response.getHits();
    long total = searchHits.getTotalHits().value;
    System.out.println(total);
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        String json = hit.getSourceAsString();
        System.out.println(json);
    }
}

You can see a lot of code repetition here. Use Ctrl+Alt+M to refactor and extract a method. The term code demonstrates this extraction.

Precise Queries

term exact match query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testTerm() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().query(QueryBuilders.termQuery("city", "上海"));
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    responseHandle(response);
}

// Extracted response handling code
private static void responseHandle(SearchResponse response) {
    SearchHits searchHits = response.getHits();
    long total = searchHits.getTotalHits().value;
    System.out.println(total);
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        String json = hit.getSourceAsString();
        System.out.println(json);
    }
}

range query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Test
public void testRange() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().query(QueryBuilders
                           .rangeQuery("price")
                           .gte(100)
                           .lte(400));
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    responseHandle(response);
}

Boolean Queries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Test
public void testBool() throws IOException {
    SearchRequest request = new SearchRequest("hotel");

    // Build the boolean query
    BoolQueryBuilder booledQuery = QueryBuilders.boolQuery();
    // Add a 'must' condition
    booledQuery.must(QueryBuilders.termQuery("city", "上海"));
    // Add a 'filter' component
    booledQuery.filter(QueryBuilders.rangeQuery("price").lte(300));

    request.source().query(booledQuery);
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    responseHandle(response);
}

Sorting and Pagination

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Test
public void testSort() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().query(QueryBuilders.matchAllQuery());
    request.source().from(10).size(10);
    request.source().sort("price", SortOrder.ASC);
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    responseHandle(response);
}

Highlighting

Highlighting differs significantly from the previous code. Here’s how to build the request:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Test
public void testHigh() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    // DSL
    request.source().query(QueryBuilders.matchQuery("all", "汉庭"));
    // Highlighting
    request.source().highlighter(
            new HighlightBuilder()
            .field("name")
            .requireFieldMatch(false)
    );
    // Send the request
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        
    // Parse
    responseHandle(response);
}

Since search results and highlighting are separate, response parsing needs additional handling.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Test
public void testHigh() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    // DSL
    request.source().query(QueryBuilders.matchQuery("all", "汉庭"));
    // Highlighting
    request.source().highlighter(
            new HighlightBuilder()
            .field("name")
            .requireFieldMatch(false)
    );
    // Send the request
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    // Parse
    SearchHits searchHits = response.getHits();
    // Total hits
    long total = searchHits.getTotalHits().value;
    System.out.println(total);
    // Document array
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        String json = hit.getSourceAsString();
        // Deserialize
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        // Get highlight results
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if(!CollectionUtils.isEmpty(highlightFields)){
            // Get highlight results
            HighlightField highlightField = highlightFields.get("name");
            if (highlightField != null){
                String name = highlightField.getFragments()[0].toString();
                // Overwrite non-highlighted content
                hotelDoc.setName(name);
            }
        }
        System.out.println(hotelDoc);
    }
}

Hotel Search Case Study

Implements four features:

  • Hotel search and pagination
  • Hotel result filtering
  • Hotels near me
  • Hotel sponsored ranking

Search and Pagination

Search Request:

  • Method: POST
  • Path: /hotel/list
  • Parameters: JSON object, containing 4 fields:
    • key: Search keyword
    • page: Page number
    • size: Page size
    • sortBy: Sort order, not implemented yet
  • Return Value: Paginated query, needs to return a PageResult containing two properties:
    • total: Total hits
    • List<HotelDoc>: Data for the current page

First, define the entity class to receive parameters.

1
2
3
4
5
6
7
@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
}

Define the return class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Data
public class PageResult {
    private Long total;
    private List<HotelDoc> hotels;

    public PageResult(){

    }

    public PageResult(Long total, List<HotelDoc> hotels) {
        this.total = total;
        this.hotels = hotels;
    }
}

Define the Controller.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@RestController
@RequestMapping("/hotel")
public class HotelController {
    @Autowired
    private IHotelService hotelService;

    @PostMapping("/list")
    public PageResult search(@RequestBody RequestParams params){
        return hotelService.search(params);
    }
}

Implement the search business. First, register a Bean object.

1
2
3
4
5
6
@Bean
public RestHighLevelClient client(){
    return new RestHighLevelClient(RestClient
            .builder(HttpHost.create("http://ip:9200")
            ));
}

Write the logic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public PageResult search(RequestParams params) {
    // Request
    SearchRequest request = new SearchRequest("hotel");
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // DSL
    String key = params.getKey();
    if (key == null || "".equals(key)){
        boolQuery.must(QueryBuilders.matchAllQuery());
    }else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // Pagination
    int page = params.getPage();
    int size = params.getSize();
    request.source().from((page - 1) * size).size(size);
    // Query
    request.source().query(boolQuery);
    // Send request
    try {
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // Parse response
        SearchHits searchHits = response.getHits();
        // Total
        long total = searchHits.getTotalHits().value;
        // Documents
        SearchHit[] hits = searchHits.getHits();
        // Iterate
        List<HotelDoc> hotels = new ArrayList<>();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            hotels.add(hotelDoc);
        }
        return new PageResult(total, hotels);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

Result Filtering

Filtering conditions include:

  • brand: Brand value
  • city: City
  • minPrice~maxPrice: Price range
  • starName: Star rating

Modify the entity class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    // Below are the newly added filter parameters
    private String city;
    private String brand;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
}

Modify the query conditions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public PageResult search(RequestParams params) {
    // Request
    SearchRequest request = new SearchRequest("hotel");
    basicQuery(params, request);
    // Pagination
    int page = params.getPage();
    int size = params.getSize();
    request.source().from((page - 1) * size).size(size);

    // Send request
    try {
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // Parse response
        SearchHits searchHits = response.getHits();
        // Total
        long total = searchHits.getTotalHits().value;
        // Documents
        SearchHit[] hits = searchHits.getHits();
        // Iterate
        List<HotelDoc> hotels = new ArrayList<>();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            hotels.add(hotelDoc);
        }
        return new PageResult(total, hotels);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

private static void basicQuery(RequestParams params, SearchRequest request) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // Input content
    String key = params.getKey();
    if (key == null || "".equals(key)){
        boolQuery.must(QueryBuilders.matchAllQuery());
    }else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // Brand
    if (params.getBrand() != null && !params.getBrand().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // Star rating
    if (params.getStarName() != null && !params.getStarName().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // City
    if (params.getCity() != null && !params.getStarName().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // Price
    if (params.getMinPrice() != null && params.getMaxPrice() != null){
        boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
    }
    // Query
    request.source().query(boolQuery);
}

Hotels Near Me

Sort nearby hotels by distance, based on location coordinates.

Modify the entity class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String city;
    private String brand;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
    // My current geographic coordinates
    private String location;
}

Add distance sorting.

1
2
3
4
5
6
7
8
if (params.getLocation() != null) {
    // Distance sort
    request.source().sort(SortBuilders
            .geoDistanceSort("location", new GeoPoint(params.getLocation()))
            .order(SortOrder.ASC)
            .unit(DistanceUnit.KILOMETERS)
    );
}

Distance Display

Modify HotelDoc, add distance.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    // Distance
    private Object distance;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}

Modify response handling.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for (SearchHit hit : hits) {
    String json = hit.getSourceAsString();
    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    Object[] sortValues = hit.getSortValues();
    if (sortValues.length > 0){
        Object sortValue = sortValues[0];
        hotelDoc.setDistance(sortValue);
    }
    hotels.add(hotelDoc);
}

Adding Sponsored Hotels

Requirement: Pin specified hotels to the top of search results.

Add a flag to specific hotels, and use this flag in filter conditions to determine if function_score should be boosted.

Add an ad flag field to HotelDoc.

1
private Boolean isAD;

Use DSL to add flags to some hotels.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Add ad
POST /hotel/_update/607915
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/728461
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/7094829
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/198323591
{
    "doc": {
        "isAD": true
    }
}

Add a score function query, modify the basicQuery() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static void basicQuery(RequestParams params, SearchRequest request) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // Input content
    String key = params.getKey();
    if (key == null || "".equals(key)){
        boolQuery.must(QueryBuilders.matchAllQuery());
    }else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // Brand
    if (params.getBrand() != null && !params.getBrand().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // Star rating
    if (params.getStarName() != null && !params.getStarName().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // City
    if (params.getCity() != null && !params.getStarName().equals("")){
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // Price
    if (params.getMinPrice() != null && params.getMaxPrice() != null){
        boolQuery.filter(QueryBuilders
                .rangeQuery("price")
                .gte(params.getMinPrice())
                .lte(params.getMaxPrice())
        );
    }

    // Score function_score
    FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
            // Original query
            boolQuery,
            // Array
            new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                    // One of the function score elements
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            // Filter condition
                            QueryBuilders.termQuery("isAD", true),
                            // Score function
                            ScoreFunctionBuilders.weightFactorFunction(10)
                    )
            }
    );
    // Query
    request.source().query(functionScoreQuery);
}