Hibernate Search Backends

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Hibernate Search Fundamentals covers the mapping annotations and search DSL, both backend-agnostic. This page covers choosing and configuring the backend that actually stores and searches the index — for defining custom analyzers themselves (n-grams, language-specific stemming, autocomplete, dynamic per-document language selection), see Hibernate Search Analyzers.

Lucene backend — embedded, per-node

The Lucene backend runs Apache Lucene directly inside the application’s own JVM, writing its index to the local filesystem of whichever node the application instance runs on. No separate service to deploy or operate — the simplest possible setup, and adequate for a single-instance deployment.

It does not scale horizontally: each application instance keeps its own, independently-maintained index on its own local disk. Running three instances of the same application means three separate, drifting copies of the index, each reflecting only the writes that particular instance’s own Hibernate Search integration happened to apply — there is no shared, near-real-time refresh across instances, and a full reindex has to run separately, per node. This makes the Lucene backend a poor fit the moment a deployment needs more than one application instance for availability or load.

The Elasticsearch/OpenSearch backend talks to a genuinely separate, shared search cluster over HTTP — every application instance indexes into, and searches, the same cluster. This is the recommended backend for any multi-instance/scaled-out deployment: the index is consistent regardless of which application instance served a given write or read, and the search cluster scales independently of the application tier (add Elasticsearch nodes without touching application instance count, or vice versa).

<dependency>
    <groupId>org.hibernate.search</groupId>
    <artifactId>hibernate-search-backend-elasticsearch</artifactId>
</dependency>
hibernate:
  search:
    backend:
      hosts: elasticsearch:9200
      version: "9"

The sharding/replica configuration on the Elasticsearch side (see Elasticsearch's own shard/replica coverage) applies to Hibernate Search’s indexes exactly as it would to any other Elasticsearch index — Hibernate Search creates and manages the index’s mapping, but the cluster-level distribution characteristics are ordinary Elasticsearch concerns.

Coordination strategies for multi-node automatic indexing

Even with a shared Elasticsearch backend, something has to decide when an application instance’s entity change gets pushed to the index, and coordinate that across instances without duplicate or lost updates:

Strategy Behavior

none

Each application instance indexes its own changes directly and synchronously (or asynchronously per the sync strategy below) as they happen, with no cross-instance coordination layer. Simplest, adequate when index updates do not need to survive an application-instance crash between the database commit and the index write.

outbox-polling

Entity changes are first written to a database "outbox" table within the same transaction as the entity change itself (transactionally consistent with it), then a background process polls that table and applies the corresponding index updates. Survives an application-instance crash between commit and indexing (the outbox row is still there to process after restart, by this or another instance), and coordinates multiple application instances without them stepping on each other — the recommended strategy for a multi-node deployment that needs indexing to be reliable across restarts.

Sync vs. async index writes

Independent of coordination strategy, an indexing operation can be synchronous (the entity-change transaction does not complete until the index write is acknowledged — index and database are never observably out of sync, at the cost of added write latency) or asynchronous (the index write happens after the transaction commits, in the background — lower write latency, with a brief window where the database has the new state but the index does not yet). Read-after-write consistency requirements on the search results should drive this choice; most applications tolerate a short async indexing lag in exchange for not blocking the write path on the search cluster’s own latency.

Spring Boot configuration

Hibernate Search’s own properties are set the same way as any other Hibernate property, via spring.jpa.properties.hibernate.search.*:

spring:
  jpa:
    properties:
      hibernate:
        search:
          backend:
            hosts: elasticsearch:9200
          coordination:
            strategy: outbox-polling

Lucene vs. Elasticsearch, side by side

A single-node embedded Lucene index compared with a shared Elasticsearch cluster behind several application instances