Java client version of the REST CompletionSuggestion

I've been tasked to upgrade code from RestHighLevelClient to the ES 8 Java Client. Most of it has been pretty smooth, until I got to our CompletionSuggestion code.

Each record in the index has this field:

    "suggest" : {
      "type" : "completion",
      "analyzer" : "simple",
      "preserve_separators" : true,
      "preserve_position_increments" : true,
      "max_input_length" : 50
    }

When the user starts typing in the search field, we present them with a list of possible search terms to use, based on results returned from the above "suggest" field.

Our current code:

    List<String> result = new ArrayList<>();
    CompletionSuggestionBuilder csb = new CompletionSuggestionBuilder("suggest");
    csb.text(query).size(100);  // query is the incoming search text

    SearchSourceBuilder ssb = new SearchSourceBuilder();
    SearchRequest sr = new SearchRequest(index);
    ssb.suggest(new SuggestBuilder()
            .addSuggestion("Suggestion", csb));
    sr.source(ssb);
    SearchResponse resp = rhlClient.search(sr, RequestOptions.DEFAULT);

    if (resp.getSuggest().size() == 0) {
        return result;
    }
    
    Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestionBldr = resp.getSuggest().getSuggestion("Suggestion");
    Iterator<? extends Suggest.Suggestion.Entry.Option> iterator =
            suggestionBldr.iterator().next().getOptions().iterator();
    // We then just loop through the iterator and build the result

I have tried using the new CompletionSuggester, along with other new options, but each tutorial or how-to I've seen/attempted returns a list of the the full records, not a list of search terms like we were getting before.

How can I get the Java Client to return a list of suggested terms, instead of the full record?

From Elasticsearch to Elastic Search

Removed language-clients

Hello and welcome!
Yes the new CompletionSuggester is a bit convoluted in terms of usage, to map it as precisely as possible to the various responses the server can send we relied heavily on generics, thus the complexity of retrieving the results - we'll try to make it easier in the future.

I implemented the Completion Suggester example in the documentation with the new java client, and it seems to be working as expected, here is the code for the search:

SearchResponse suggestResponse = esClient.search(s -> s
    .index("music")
    .suggest(su -> su
        .suggesters("song-suggest", sg -> sg
            .completion(c -> c
                .field("suggest"))
            .prefix("nir"))), MusicSuggest.class);

MusicSuggest is a custom class created to map this document:

  "suggest" : {
    "input": [ "Nevermind", "Nirvana" ],
    "weight" : 34
  }

so something like this (with getters and setters):

public class MusicSuggest {
    private Music suggest;
}

class Music{
    private List<String> input;
    private int weight;
}

to get the list of suggestions from the SearchResponse:

List<Suggestion> suggestions = (List<Suggestion>) suggestResponse.suggest().get("song-suggest");
List<CompletionSuggestOption> options = suggestions.get(0).completion().options();
MusicSuggest originalDocument = (MusicSuggest) options.get(0).source();
List<String> stringSuggestions = originalDocument.getSuggest().getInput();

of course imagine the lists being streamed/iterated instead of just reading the first value, but this should give an idea on how to proceed. Let me know if it works for your case!

Hello Laura. Thank you for the response and suggestions. I was able to get this solution implemented and functioning, however it still isn't getting me what I need.

I'm getting a set of entire records, of which "suggest" is just one of many fields on each record. When I go through the results as you've indicated, I'm presented with the entire list of "suggest:input" strings.

In your example, when using a prefix of "nir", I am needing only "Nirvana" to be returned. What I'm getting is the entire record, with the .getSuggest().getInput() returning ["Nevermind","Nirvana"].

I can point you to exactly what I am needing because our old RestHighLevelClient code is out for the world to see. If you go to NRD and start typing in the search field, you will see a list of up to 5 possible suggestions on what you might want to search for.

I decided trying out the search directly in Kibana to see if I could figure out what search settings will return what I needed. Lo and behold, I found the solution!

The documentation, along with Laura's help, was almost exactly right. The problem was in retrieving the results. What I needed was not in this:

MusicSuggest originalDocument = (MusicSuggest) options.get(0).source();
List<String> stringSuggestions = originalDocument.getSuggest().getInput();

The above just provided the entire set of records that matched the query. What I needed was to, after this code:

List<Suggestion> suggestions = (List<Suggestion>) suggestResponse.suggest().get("song-suggest");
List<CompletionSuggestOption> options = suggestions.get(0).completion().options();

simply walk the options as such:

for (CompletionSuggestOption o : options) {
    result.add(o.text());
}

The text field provides the actual match of the query prefix.

1 Like