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.xml marking 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

none

Hibernate does not touch the schema at all — the recommended value in production, where schema changes are applied through a dedicated migration tool instead.

create

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.

drop-and-create

Drops the whole schema and recreates it from the current entity mappings on every startup — convenient in quarkus:dev and for tests, never in production.

update

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 ALTER statements are not reviewed or version-controlled.

validate

Compares the entity mappings against the existing schema and fails startup on a mismatch, without changing anything — the recommended value in production alongside none when schema changes are applied out-of-band.

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

DATABASE

Each tenant’s data lives in a completely separate database/datasource; the application supplies a TenantConnectionResolver that returns the right AgroalDataSource for the current tenant.

SCHEMA

Each tenant’s data lives in its own schema within the same database; the application supplies a TenantResolver (or a TenantConnectionResolver) that returns the current tenant’s schema name.

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.