
This post is part of a series:
Part 3 turned a q string into
a list of beans. The search box still feels incomplete until typing sin offers
Sinclar before the user hits Enter.
You already declared lucene-suggest in Part 1.
Skip this post entirely if you only need search and filters — the index in Parts 1–3
does not depend on it.
A second Directory
AnalyzingInfixSuggester is not an IndexSearcher on the track index. It is a
dictionary with its own Directory (in-memory is fine for a local library).
Use the same analyzer as TextField so prefixes match how titles were tokenized.
// Dedicated Directory — do not share it with the track IndexWriter
Directory suggestionDirectory = new ByteBuffersDirectory();
AnalyzingInfixSuggester suggester =
new AnalyzingInfixSuggester(suggestionDirectory, TrackAnalyzers.searchAnalyzer());
Keep it next to TrackSearchIndex, behind the same write lock as rebuild / upsert.
The writer is thread-safe; the suggester rebuild is not something you want racing
a lookup.
Rebuild after writes
Incremental delete on an infix suggester is awkward. For a few thousand beans, rebuild the dictionary from the current map after every mutation — the same budget as the ~400 ms track-index rebuild in Part 2.
// InputIterator: text to suggest + optional payload (field name, id, …)
suggester.build(new TrackSuggestionIterator(tracks));
Feed title, artist, and genre (whatever you show in the dropdown). Deduplicate if the same string appears on several tracks.
Lookup
// prefix → up to 10 suggestions; last two flags: allTermsRequired, highlight
List<Lookup.LookupResult> matches =
suggester.lookup(prefix, Set.of(), 10, false, false);
Payloads can carry metadata — for example which field matched (title / artist /
genre) so the UI can group or iconify rows. The lookup string is a prefix, not
the full q grammar from Part 3: keep “typeahead” and “run search” as two calls.
On the web side, debounce the input and hit a small endpoint that returns those
rows. Selecting a suggestion either fills the box or navigates straight to
filter(corpus, suggestedText).
Close
Close the suggester when you close the index (Part 2), after the IndexWriter.
A process restart rebuilds both from the source of truth anyway.
Next
Autocomplete sits beside search, not instead of it. Part 5 will count facet
buckets under the same q so a filter panel can show Club (12) instead of a
blind checkbox list.
Series
- Part 1: Indexing — Maven, fields, analyzer, document mapper
- Part 2: Index Lifecycle — writer, rebuild, upsert, keep warm
- Part 3: Search — queries, hits → beans
- Part 4: Suggest — you are here
- Part 5: Facets — counts and drill-down
