Hibernate Reactive and Data Repositories
|
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
This section’s bibliography lists the reference material consulted while preparing these pages. |
Everything else in this section documents blocking Hibernate ORM. This page covers Hibernate Reactive — a genuinely different, non-blocking execution model — and Hibernate Data Repositories, the Jakarta Data implementation built on blocking Hibernate ORM. The two are easy to conflate by name; they are unrelated to each other beyond sharing the Hibernate project.
Hibernate Reactive fundamentals
Hibernate Reactive offers two async APIs: the recommended, Mutiny-based Mutiny.SessionFactory/
Mutiny.Session, and a legacy CompletionStage-based Stage.SessionFactory/Stage.Session. This page uses
the Mutiny API throughout, as the reference documentation itself recommends.
Two facts shape everything else on this page:
-
Hibernate Reactive requires a genuinely non-blocking Vert.x reactive SQL client per database (dedicated clients exist for PostgreSQL, MySQL, DB2, SQL Server, Oracle, and CockroachDB) instead of a JDBC driver — there is no JDBC
DataSourceunderneath at all, so none of the connection-pool/DataSourceconfiguration used elsewhere in this section applies. -
Spring Data does not support Hibernate Reactive — there is no
ReactiveCrudRepository-style repository abstraction for it. Every reactive interaction with Hibernate Reactive goes through theMutiny.SessionFactoryAPI directly.
Obtaining the Mutiny.SessionFactory: unwrap it from a JPA EntityManagerFactory built with the
org.hibernate.reactive.provider.ReactivePersistenceProvider persistence provider, then expose it as a Spring
@Bean:
@Configuration
public class HibernateReactiveConfig {
@Bean
public Mutiny.SessionFactory sessionFactory() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("library-reactive");
return emf.unwrap(Mutiny.SessionFactory.class);
}
}
Accessing the session/entity manager reactively
Mutiny.SessionFactory has no standalone "get me a session" call the way blocking Hibernate’s
@PersistenceContext EntityManager em does. Instead, the session exists only for the duration of a callback’s
Uni:
// read / non-transactional work
public Uni<Book> findBook(Long id) {
return sessionFactory.withSession(session -> session.find(Book.class, id));
}
// transactional work -- see "Transaction handling" below
public Uni<Order> placeOrder(Order order) {
return sessionFactory.withTransaction((session, tx) ->
session.persist(order).chain(session::flush).replaceWith(order));
}
withSession(Function<Mutiny.Session, Uni<T>> work) opens (or reuses) a Mutiny.Session for the duration of
work, returning the Uni<T> the callback produces. withTransaction(BiFunction<Mutiny.Session,
Mutiny.Transaction, Uni<T>> work) does the same within a transaction. A nested withSession/withTransaction
call made from inside an already-open one reuses the same session rather than opening a second one — calling
withTransaction again inside an outer withTransaction block joins the existing transaction/session instead
of nesting a new one.
How the session is bound to the current unit of work — and why this differs from Reactor
This is the detail every other page in this section’s cross-linked
Reactive Programming page does not prepare you for.
Per the Hibernate Reactive reference documentation, withSession()/
withTransaction() associate the reactive session with the current Vert.x (duplicated) Context — not
a Reactor Context, and not a ThreadLocal. Every Hibernate Reactive call must run on a Vert.x event-loop
thread; calling from any other thread fails with:
HR000068: This method should exclusively be invoked from a Vert.x EventLoop thread
Contrast this directly with
Reactive
transactions and thread affinity, which documents how Spring’s own ReactiveTransactionManager rides the
Reactor Context instead — state that survives thread hops, scoped to one unit of work rather than one
thread. The two mechanisms look similar at that level of description, but they are two entirely independent
context-propagation systems that share no state whatsoever. A Reactor pipeline calling into Hibernate Reactive
crosses a real context boundary at that call, even though no explicit .subscribe() is written there — the
"don’t break the chain" rules on that page are about Reactor’s own Context and say nothing, by themselves,
about the Vert.x Context Hibernate Reactive needs to already be present.
Transaction handling
withTransaction((session, tx) → …) demarcates the transaction programmatically, not declaratively: it
begins a transaction, runs the callback, and commits when the returned Uni completes successfully, or rolls
back if it completes with a failure — including an exception thrown synchronously inside the callback.
Conceptually this is the Mutiny counterpart of TransactionalOperator.transactional(…)
(Reactive
transactions and thread affinity), but there is no ReactiveTransactionManager implementation for Hibernate
Reactive, and Spring’s declarative @Transactional on a method returning a Publisher does not apply to
it — withTransaction is the only transaction-demarcation mechanism available.
public Uni<Order> placeOrder(Order order) {
return sessionFactory.withTransaction((session, tx) ->
session.persist(order).chain(session::flush).replaceWith(order));
}
Bridging Uni/Multi to Mono/Flux
The io.smallrye.reactive:mutiny-reactor Maven artifact (see
the SmallRye Mutiny "Using other reactive programming
libraries" guide) provides UniReactorConverters/MultiReactorConverters for the Hibernate-Reactive-to-Spring
direction:
public Mono<Book> findBookReactor(Long id) {
Uni<Book> uni = sessionFactory.withSession(session -> session.find(Book.class, id));
return uni.convert().with(UniReactorConverters.toMono());
}
public Flux<Book> allBooksReactor() {
Multi<Book> multi = sessionFactory.withSession(session ->
session.createQuery("from Book", Book.class).getResultList())
.onItem().transformToMulti(list -> Multi.createFrom().iterable(list));
return multi.convert().with(MultiReactorConverters.toFlux());
}
For the reverse direction (Reactor to Mutiny), plain Reactive-Streams interop needs no extra dependency:
Uni.createFrom().publisher(mono) / Multi.createFrom().publisher(flux).
The bridge converts only the value stream — it does not carry Reactor Context into the Mutiny side
or vice versa (per the context-boundary point above). Anything read from Reactor Context upstream of the
conversion must be passed into the Hibernate Reactive call explicitly, as a method parameter — never assumed
to cross the bridge implicitly.
Running under Spring WebFlux specifically
The Vert.x Context requirement is independent of which HTTP server WebFlux uses. Spring WebFlux’s default
Reactor Netty server does not itself provide a Vert.x event loop, so an application embeds/starts its own
io.vertx.core.Vertx instance and runs Hibernate Reactive calls on it explicitly (e.g. via
vertx.getOrCreateContext() / Vertx.currentContext().runOnContext(…)), rather than assuming the WebFlux
request thread already is one.
Quarkus’s Hibernate Reactive guide is worth mentioning only as a contrast: Quarkus wires this automatically because it runs on Vert.x natively. This section does not use Quarkus as part of its own stack — the point above about explicitly embedding Vert.x is what a Spring WebFlux application specifically needs to do that a Quarkus application does not.
Hibernate Data Repositories
Separately from Hibernate Reactive, Hibernate ORM 7 implements the Jakarta Data @Repository model:
implementations generated at compile time by HibernateProcessor, using @Find/@Query/@HQL/@SQL
annotated methods, backed by StatelessSession.
@Repository
public interface BookRepository {
@Find
List<Book> findByAuthorName(String authorName);
@HQL("from Book where publishedYear > :year")
List<Book> recentBooks(int year);
}
State plainly: this is the blocking-Hibernate-ORM Jakarta Data implementation, not Hibernate Reactive — it
runs on StatelessSession over a normal JDBC connection, with none of the Vert.x-Context requirements above.
It differs in intent from Spring Data JPA in that Jakarta Data is a specification-level, provider-agnostic
repository model (any Jakarta Data provider could implement BookRepository above), whereas Spring Data JPA’s
JpaRepository is Spring-specific and Hibernate/JPA-specific by design.
Hibernate Search does not support Hibernate Reactive
Per the Hibernate team, hibernate-search-mapper-orm does not work with Hibernate Reactive, and there is no
officially supported or planned reactive Hibernate Search integration — tracked as the still-open feature
request HSEARCH-4922, with no committed timeline (see also
the Hibernate team’s own confirmation
on the Hibernate community forum).
For full-text search from a Hibernate-Reactive-based service, call Elasticsearch/OpenSearch directly with a non-blocking client instead of going through Hibernate Search’s ORM mapper — see Hibernate Search Backends (explicitly noting it is blocking-Hibernate-only) and Elasticsearch for the reactive Elasticsearch client / Spring Data Elasticsearch reactive path.
Cross-references
See also Reactive Programming, anchored specifically at Reactive transactions and thread affinity (per the context-boundary discussion above) and at Bridging to blocking code safely for the general principle it already establishes — that a hard technology boundary (there: a blocking call; here: a different reactive runtime’s own context) must be crossed deliberately and explicitly, never assumed.
Reactor chain to Vert.x-bound session and back
Vert.x Context begins here Bridge->>Session: sessionFactory.withTransaction(session, tx, work) Session->>Client: non-blocking SQL call bound to current Vert.x Context Client->>DB: query/update DB-->>Client: result Client-->>Session: Uni of the result Session-->>Bridge: Uni of the result Bridge-->>Controller: UniReactorConverters.toMono(): back into Reactor Context
Links
-
SmallRye Mutiny — Using other reactive programming libraries
-
HSEARCH-4922 — Hibernate Search / Hibernate Reactive integration
-
Quarkus — Hibernate Reactive guide (contrast only, not part of this section’s own stack)