Search your beans with Lucene — Suggest

Tuesday, Sep 15, 2026 | 2 minute read

David Pilato
Search your beans with Lucene — Suggest

We have seen so far how to run a search query on whatever field you mapped. Anyone used to a search engine already knows the next step: autocomplete, so the index can help you find the right query.

Autocomplete lives in its own artefact:

<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-suggest</artifactId>
  <version>10.5.1</version>
</dependency>

A second Directory

AnalyzingInfixSuggester is not an IndexSearcher on the track index. It is a dictionary with its own Directory. Use the same analyzer as before so prefixes match how titles were tokenized:

Directory suggestionDirectory = new ByteBuffersDirectory();
AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(
        suggestionDirectory, analyzer);
suggester.build(new TrackSuggestionInputIterator(tracks.values()));

Keep it next to the track writer, behind the same write lock as rebuild / upsert. Incremental delete on an infix suggester is awkward; for a few thousand beans, rebuild the dictionary after every mutation.

Type “club”

Prefix club. Hover a row to see lookup() → highlightKey, field, FILTER chip.

Prefix club. Hover a row to see lookup()highlightKey, field, FILTER chip.

List<Lookup.LookupResult> matches =
        suggester.lookup("club", Set.of(), 10, true, true);
Lookup.LookupResult match = matches.get(0);
String text = match.key.toString();              
// "Club House"
String field = match.payload.utf8ToString();     
// "genre"
String highlight = match.highlightKey.toString(); 
// "<b>Club</b> House"

This means that the first suggestion is Club House coming from the genre field and could be presented to the user as:

Club House.

Filter with the suggestion

Selecting Club House (field = "genre") could then call your backend search API: /tracks?genre=Club House and update the UI to reflect the selected filter chip.

If you want suggestions to respect the current filter set, change the call:

List<Track> scoped = search.filter(corpus, "", filters, mustNots);
List<TrackSuggestion> hits = index.suggest(prefix, scoped);

filter(..., "", filters, mustNots) applies the chips already on, with an empty query string. suggest(prefix, scoped) then looks up only inside that subset — so club cannot propose a genre that those chips already excluded.

© 2010 - 2026 David Pilato

Search is powered by Pagefind. Just hit CTRL+K or CMD+K to start searching.

Powered by Hugo with Dream and Devrel themes.

Details

I discovered Elasticsearch project in 2011. After contributed to the project and created open source plugins for it, David joined elastic the company in 2013 where he is Developer and Evangelist. He also created and still actively managing the French spoken language User Group. At elastic, he mainly worked on Elasticsearch source code, specifically on open-source plugins. In his free time, he likes talking about elasticsearch in conferences or in companies (Brown Bag Lunches AKA BBLs). He is also author of FSCrawler project which helps to index your pdf, open office, whatever documents in elasticsearch using Apache Tika behind the scene.

Who am I?

Developer | Evangelist at elastic and creator of the Elastic French User Group. Frequent speaker about all things Elastic, in conferences, for User Groups and in companies with BBL talks. In my free time, I enjoy coding and deejaying as DJ Elky, just for fun. Living with my children in Cergy, France.

Social Links