Hibernate ORM in Quarkus
|
This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against. Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change. This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page covers only what Quarkus adds to, changes about, or constrains on top of Hibernate ORM/JPA itself — how the quarkus-hibernate-orm extension is set up and configured, Quarkus’s no-persistence.xml model and
entity discovery rules, schema generation, second-level caching, statistics, multitenancy, and the build-time
metamodel and native-image implications specific to running Hibernate under Quarkus. Hibernate ORM/JPA
fundamentals — entity mapping, associations, HQL, the Criteria API, caching theory, and the persistence-context
lifecycle — are not re-documented here; see Hibernate Reference for those.
Extension setup and the no-persistence.xml model
Adding the extension and a JDBC driver
Two extensions expose Hibernate ORM in Quarkus: plain quarkus-hibernate-orm for the classic
jakarta.persistence.EntityManager style, and quarkus-hibernate-orm-panache for Panache’s active-record/
repository style on top of the same Hibernate ORM engine. This page only mentions that Panache exists — Panache: The Quarkus Data API documents it in full and is not repeated here. Either extension needs a
JDBC driver extension for the actual database in use:
# classic EntityManager style
./mvnw quarkus:add-extension -Dextensions=hibernate-orm,jdbc-postgresql
# or, for Panache's active-record/repository style instead (see xref:backend/quarkus/panache.adoc[])
./mvnw quarkus:add-extension -Dextensions=hibernate-orm-panache,jdbc-postgresql
which adds to pom.xml:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
Configuring the datasource and Hibernate ORM
A minimal working setup needs both a datasource (covered in full on
the datasources and transactions page, not repeated
here) and a handful of quarkus.hibernate-orm.* keys:
# Datasource -- see xref:backend/quarkus/datasources-and-transactions.adoc[] for the full set of options.
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=app
quarkus.datasource.password=secret
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/app
# Hibernate ORM itself.
quarkus.hibernate-orm.database.generation=validate
quarkus.hibernate-orm.log.sql=false
quarkus.hibernate-orm.packages=com.example.book
quarkus.hibernate-orm.packages narrows entity discovery to specific packages when needed; leaving it unset
discovers every @Entity on the application classpath, as described next.
No persistence.xml: entities discovered from the classpath, configuration from application.properties
A plain JPA application ships a META-INF/persistence.xml naming its persistence unit, its entity classes (or
relying on JAR scanning), and its provider properties. Quarkus does not use this model by default: at build
time, Quarkus’s Jandex-based indexing scans the application and its dependencies for every class annotated
jakarta.persistence.Entity, and generates the persistence unit automatically — there is no
persistence.xml to write, and every quarkus.hibernate-orm.* property in application.properties configures
that generated unit directly.
An explicit persistence.xml is still worth writing in two situations: an application genuinely needs multiple
persistence units with fine-grained, JPA-spec-level control that the quarkus.hibernate-orm."<unit-name>".*
properties (see Multiple persistence units) don’t expose, or the persistence unit must stay portable to a
non-Quarkus JPA runtime. In either case, set:
quarkus.hibernate-orm.persistence-xml.ignore=false
quarkus.hibernate-orm.persistence-xml.ignore defaults to true — Quarkus ignores a persistence.xml present
on the classpath and builds its own generated unit instead, unless this property is explicitly set to false to
opt back into the standard JPA bootstrap.
Entity discovery and entities in external JARs
Automatic entity discovery relies on Jandex, the bytecode indexer Quarkus’s build process runs over the
application module and every dependency JAR. A dependency JAR that itself is a Quarkus extension, or that
ships its own META-INF/jandex.idx, is indexed automatically. A plain, non-Quarkus-aware JAR containing
@Entity classes is not indexed by default, so its entities are invisible to discovery unless one of two
things is true:
-
the JAR carries a
META-INF/beans.xmlmarking it as a CDI bean archive, which Quarkus also treats as a signal to index it; or -
the application forces indexing of that dependency explicitly:
quarkus.index-dependency.book-shared-model.group-id=com.example
quarkus.index-dependency.book-shared-model.artifact-id=book-shared-model
book-shared-model above is an arbitrary local name for the quarkus.index-dependency.* entry, not part of the
target JAR’s own coordinates — it only groups the group-id/artifact-id pair (and, optionally,
classifier) that identify which dependency to index.
Dialect detection
Hibernate ORM needs a Dialect to generate database-specific SQL. Quarkus derives it automatically from
quarkus.datasource.db-kind (postgresql, mysql, h2, and so on) — there is normally no separate dialect
property to set at all. Overriding it explicitly is only needed for a database version whose SQL dialect
differs meaningfully from what the detected default assumes:
quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQLDialect
Schema generation, caching, and multitenancy
Schema generation and seed data
quarkus.hibernate-orm.database.generation controls what Hibernate does to the schema on startup:
| Value | Behavior |
|---|---|
|
Hibernate does not touch the schema at all — the recommended value in production, where schema changes are applied through a dedicated migration tool instead. |
|
Drops nothing first, but creates tables/constraints for entities that don’t already have them; leftover data from a previous run can conflict with the newly created schema. |
|
Drops the whole schema and recreates it from the current entity mappings on every startup — convenient in
|
|
Attempts to alter the existing schema to match the entity mappings incrementally; useful for quick local
iteration, but not a substitute for a real migration tool since its generated |
|
Compares the entity mappings against the existing schema and fails startup on a mismatch, without changing
anything — the recommended value in production alongside |
A typical split keeps schema management effectively disabled in production while letting dev mode manage its own throwaway database:
quarkus.hibernate-orm.database.generation=none
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%test.quarkus.hibernate-orm.database.generation=drop-and-create
import.sql on the classpath root (src/main/resources/import.sql) is executed once, right after schema
generation, whenever database.generation is create, drop-and-create, or update — a convenient place for
seed data in dev mode or tests:
INSERT INTO book (id, title, author) VALUES (1, 'Dune', 'Frank Herbert');
INSERT INTO book (id, title, author) VALUES (2, 'Foundation', 'Isaac Asimov');
A different file name or location is set via:
%dev.quarkus.hibernate-orm.sql-load-script=dev-seed.sql
%test.quarkus.hibernate-orm.sql-load-script=no-file
quarkus.hibernate-orm.sql-load-script=no-file disables loading any script at all — useful per-profile when a
particular environment must not run seed data even though database.generation would otherwise trigger it.
Second-level caching
Quarkus ships Hibernate ORM’s own built-in second-level cache provider, enabled per-entity by annotating it
jakarta.persistence.Cacheable and turning caching on globally:
quarkus.hibernate-orm.cache."com.example.book.Book".expiration.max-idle=1H
quarkus.hibernate-orm.cache."com.example.book.Book".memory.object-count=1000
package com.example.book;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
@Cacheable
public class Book {
@Id
private Long id;
private String title;
private String author;
}
This built-in L2 cache is a lightweight, JVM-local, in-memory cache tied directly into Hibernate’s own persistence-context and query-cache machinery — it is not a distributed cache and does not share state across multiple application instances. The separate Quarkus Cache extension (covered on its own page) is a different mechanism entirely — general-purpose method-level caching, with pluggable backends including distributed ones — and the two should not be conflated: reach for Hibernate’s L2 cache only for entity/query caching inside a single JVM, and for Quarkus Cache for anything that needs to scale across instances or cache non-entity method results.
Statistics and slow-query logging
quarkus.hibernate-orm.statistics=true
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.log.slow-sql=100
quarkus.hibernate-orm.statistics=true turns on Hibernate’s Statistics collector (entity/query/cache hit and
miss counts, flush counts, and so on); quarkus.hibernate-orm.log.sql=true logs every SQL statement Hibernate
executes; quarkus.hibernate-orm.log.slow-sql, given a threshold in milliseconds, logs only statements that
took at least that long, which is normally the more useful of the two for production-like diagnostics since it
does not drown the log in every trivial query. In dev mode, the same information is also available live from the
Dev UI’s Hibernate ORM panel at http://localhost:8080/q/dev/, without turning on statistics collection or SQL
logging at all.
Multiple persistence units
An application that talks to more than one database defines additional named persistence units under
quarkus.hibernate-orm."<unit-name>".*, each with its own datasource and configuration:
# default persistence unit
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/app
quarkus.hibernate-orm.packages=com.example.book
# named "reporting" persistence unit
quarkus.datasource."reporting".db-kind=postgresql
quarkus.datasource."reporting".jdbc.url=jdbc:postgresql://localhost:5432/reporting
quarkus.hibernate-orm."reporting".datasource=reporting
quarkus.hibernate-orm."reporting".packages=com.example.reporting
An injection point targets a specific unit’s EntityManager/EntityManagerFactory with the matching CDI
qualifier:
package com.example.reporting;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import io.quarkus.hibernate.orm.PersistenceUnit;
@ApplicationScoped
public class ReportingService {
@Inject
@PersistenceUnit("reporting")
EntityManager reportingEntityManager;
}
io.quarkus.hibernate.orm.PersistenceUnit is Quarkus’s own qualifier annotation for this purpose; standard
jakarta.persistence.PersistenceContext(unitName = "reporting") also works for code that needs to stay closer to
the plain JPA API.
Multitenancy
Quarkus supports Hibernate ORM’s standard multitenancy strategies via quarkus.hibernate-orm.multitenant:
| Strategy | Behavior |
|---|---|
|
Each tenant’s data lives in a completely separate database/datasource; the application supplies a
|
|
Each tenant’s data lives in its own schema within the same database; the application supplies a
|
quarkus.hibernate-orm.multitenant=SCHEMA
quarkus.hibernate-orm.multitenant-schema-datasource=<default>
package com.example.tenant;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.hibernate.orm.PersistenceUnitExtension;
import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver;
@PersistenceUnitExtension
@ApplicationScoped
public class RequestTenantResolver implements TenantResolver {
@Override
public String getDefaultTenantId() {
return "public";
}
@Override
public String resolveTenantId() {
return CurrentTenant.get();
}
}
@io.quarkus.hibernate.orm.PersistenceUnitExtension marks the bean as belonging to a specific persistence unit
(the default one here); an application with multiple persistence units and per-unit tenancy supplies one such
bean per unit, disambiguated the same way. Whichever strategy is chosen, the resolver bean is the only piece an
application must supply — schema/connection switching itself is handled by Hibernate ORM under the hood.
Build-time metamodel and native-image implications
Build-time static metamodel generation
The classic Jakarta Persistence static metamodel (Book_.class, with typed SingularAttribute/
ListAttribute fields for criteria queries) is normally produced by an annotation processor,
hibernate-jpamodelgen, running during javac compilation. Under Quarkus, the equivalent metamodel is instead
generated as part of Quarkus’s own build/augmentation steps, driven by the same Jandex index used for entity
discovery — there is no need to add hibernate-jpamodelgen as a compiler annotation processor just to make
Quarkus’s own Hibernate ORM usage work.
Adding hibernate-jpamodelgen explicitly is still worthwhile in one specific case: an IDE that runs its own
incremental javac compilation (for code completion and error highlighting on Book_ references in Criteria
API code) needs the annotation processor available on that compilation’s own classpath, independent of what
Quarkus’s augmentation phase produces at build time:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
Native-image implications
Quarkus builds native-image support for Hibernate ORM around build-time bytecode enhancement: entity classes are
enhanced during augmentation (lazy-loading proxies, dirty-checking, and so on generated directly into bytecode)
so that no reflection is needed to instantiate or manage entities at runtime. This is also why everything
Hibernate needs to know about the object model — every entity class, every mapping, every named query — must be
resolvable at build time from the classpath actually present when native-image runs.
A direct consequence is that no entity, mapping, or persistence-unit configuration can be added, generated, or
changed at runtime in a native executable: code that dynamically registers a new @Entity class, builds a
SessionFactory/EntityManagerFactory from mappings assembled on the fly, or otherwise relies on Hibernate’s
classic runtime-reflection bootstrap path will not work once compiled to a native image, even though the same
code may run correctly on the JVM. Anything Hibernate is expected to manage has to be a normal, statically
present @Entity class discoverable by Jandex at build time, exactly as described in
Entity discovery and entities in external JARs.