# 25 — Hibernate Search [← Previous: 24 — Interceptors](24-interceptors.md) | [Back to README →](../README.md) Backs the rewrite of ankurm.com post 4892 (Hibernate Search). ## The version this repo's own earlier post got wrong The previous version of this post claimed `7.3.2.Final`. Checked against `repo1.maven.org/maven2/org/hibernate/search/hibernate-search-mapper-orm/maven-metadata.xml`, the current GA line is **`8.4.0.Final`** — its own `pom.xml` depends on `hibernate-core:7.4.0.Final`, compatible with this repo's pinned `7.4.5.Final` (same major.minor line). Hibernate Search's own version numbering does not track Hibernate ORM's — Search 8.x pairs with ORM 7.x, not because "8" follows "7" but because that's simply what its own POM resolves. ## Two configuration traps that break the whole application, not just this chapter Both of these were found by actually wiring this chapter up against the real jars, not by reading about the API: 1. **Naming an analyzer that doesn't exist.** `@FullTextField(analyzer = "english")` fails application startup outright with `HSEARCH000353: Unknown analyzer: 'english'` — the Lucene backend ships no predefined analyzer under that name; a custom analyzer needs to be registered via a `LuceneAnalysisConfigurer` bean first. Omitting the `analyzer` attribute uses Hibernate Search's own built-in default, which is enough for straightforward full-text matching. 2. **`@IndexedEmbedded` on an association with no defined inverse side.** Fails bootstrap with `HSEARCH700020: Unable to find the inverse side of the association` — Hibernate Search needs to know how to find every `Movie` that embeds a given `Director` so it can reindex them when the `Director` changes. Either add a `@OneToMany(mappedBy = ...)` back-reference, or, if reindex-on-update isn't needed, opt out explicitly: ```java @ManyToOne @IndexedEmbedded @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW) private Director director; ``` Because `com.ankurm.hibernatedemo.search.Movie` is `@Indexed` and lives in this repository's normally-scanned package tree, **every** test in this repo that boots the shared Spring context now bootstraps Hibernate Search too — confirmed the hard way, by watching an unrelated, already-passing aggregate-function test fail until the backend was configured correctly. See `src/main/resources/application.yml`'s `hibernate.search.*` block and its comment. [`Movie.java`](../src/main/java/com/ankurm/hibernatedemo/search/Movie.java), [`Director.java`](../src/main/java/com/ankurm/hibernatedemo/search/Director.java) ## Field types: full-text, keyword, and generic ```java @FullTextField private String title; // analyzed, tokenized, fuzzy-matchable @KeywordField private String genre; // stored and compared as ONE whole value, never tokenized @GenericField(sortable = Sortable.YES) private int releaseYear; // plain value, explicitly opted into sorting ``` A `@KeywordField` search has to match the *entire* stored value — `"science"` does not match a stored `"Science Fiction"` the way a tokenized `@FullTextField` term would. [Test](../src/test/java/com/ankurm/hibernatedemo/search/HibernateSearchTest.java) — [keyword output](output/25-keyword-exact-match.txt), [sortable output](output/25-sortable-generic-field.txt) ## Fuzzy full-text matching ```java searchSession.search(Movie.class) .where(f -> f.match().field("title").matching("Godfaher").fuzzy(1)) .fetchHits(20); ``` `.fuzzy(1)` tolerates a one-character edit distance — a typo a plain SQL `LIKE '%Godfaher%'` would never match. [Test](../src/test/java/com/ankurm/hibernatedemo/search/HibernateSearchTest.java) — [output](output/25-fulltext-fuzzy.txt) ## `@IndexedEmbedded`: searching through an association `Director` itself carries no `@Indexed` annotation — it only appears inside `Movie`'s index because `Movie.director` is `@IndexedEmbedded`: ```java searchSession.search(Movie.class) .where(f -> f.bool() .must(f.match().field("title").matching("Iea")) .must(f.match().field("director.name").matching("Iea Christopher Nolan"))) .fetchHits(20); ``` [Test](../src/test/java/com/ankurm/hibernatedemo/search/HibernateSearchTest.java) — [output](output/25-indexed-embedded.txt) ## `MassIndexer`: rebuilding the index from the database ```java searchSession.workspace().purge(); // empty the index; the database row is untouched searchSession.massIndexer(Movie.class).startAndWait(); ``` After `purge()`, a search for a row that's still in the database returns zero hits — the index and the database are two separate stores, and nothing keeps them in sync automatically once the index falls behind. `MassIndexer` rebuilds the index straight from what's in the database, without re-persisting anything — the fix for "the index went stale" or "this table existed before Hibernate Search was added to the project." [Test](../src/test/java/com/ankurm/hibernatedemo/search/HibernateSearchTest.java) — [output](output/25-mass-indexer.txt) ## Coordination strategies, briefly This chapter uses Hibernate Search's default coordination: indexing happens synchronously, in the same thread and transaction as the entity change. For a JTA or distributed deployment, `hibernate-search-mapper-orm-coordination-outbox-polling` (confirmed to exist at the same `8.4.0.Final` line on Maven Central) writes index updates to an outbox table first and applies them asynchronously — trading immediate search-index consistency for not blocking the write transaction on indexing work. This repo's tests rely on synchronous indexing specifically so a search immediately after a `commit()` is guaranteed to see the new data; that guarantee does not hold under the outbox strategy without an explicit wait. ## Going deeper - Automatic indexing tracks entity changes through Hibernate's own event system — a bulk HQL/SQL mutation bypasses it exactly the same way it bypasses interceptor callbacks (chapter 24); reindex explicitly (or via `MassIndexer`) after any bulk write. - `hibernate-search-backend-elasticsearch` is a drop-in alternative to the Lucene backend used here for a deployment that already runs Elasticsearch or OpenSearch — the annotations on `Movie`/`Director` do not change; only the `pom.xml` dependency and backend properties do. - [Hibernate Search 8.4 reference documentation](https://docs.jboss.org/hibernate/search/8.4/reference/en-US/html_single/)