This guide discusses migration to Hibernate ORM version 8.0. For migration from earlier versions, see any other pertinent migration guides as well.

Requirements

See the website for the list of requirements for the 8.0 series.

Jakarta Persistence 4.0

One specific requirement change that is important to call out is the move to from Jakarta Persistence version 3.2 to 4.0 which has a number of impacts, many of which are discussed below.

See this issue for more details.

Jakarta Validation lifecycle event changes

Jakarta Persistence 4.0 redefines which lifecycle events trigger automatic bean validation. Previously, validation was tied to pre-persist and pre-update events. Now, validation is organized into EntityManager-level events (pre-persist, pre-merge, pre-remove) and DB-level events (pre-insert, pre-update, pre-upsert, pre-delete), each with its own configuration property.

By default, only pre-insert, pre-update, and pre-upsert trigger validation with the jakarta.validation.groups.Default group. All other events (pre-persist, pre-merge, pre-remove, pre-delete) have empty groups and do not validate unless explicitly configured.

The following new configuration properties are available:

  • jakarta.persistence.validation.group.pre-merge

  • jakarta.persistence.validation.group.pre-insert

  • jakarta.persistence.validation.group.pre-upsert

  • jakarta.persistence.validation.group.pre-delete

Breaking change: the property jakarta.persistence.validation.group.pre-persist previously was enabled by default. It is now responsible for the validation at the pre-persist lifecycle event and is empty by default. Applications that relied on pre-persist to configure insert-time validation groups should switch to pre-insert, especially if the application plans to rely on usage of a stateless session.

New Features

See the website for the list of new features in the 8.0 series.

Changes to API

This section describes changes to contracts (classes, interfaces, methods, etc.) which are considered API.

Removed Session.replicate()

The deprecated Session.replicate() methods and the associated ReplicationMode enum have been removed. There is no direct replacement. For some import or overwrite workflows, consider StatelessSession#upsert(Object).

StatelessSession

Jakarta Persistence now defines a contract for "stateless" processing, named EntityAgent, similar to Hibernate’s StatelessSession. Hibernate’s StatelessSession now implements EntityAgent. 2 methods on EntityAgent clash with methods already defined on StatelessSession -

  • insert() - EntityAgent defines a void return type, whereas Hibernate has historically returned the identifier of the inserted entity.

  • fetch() - EntityAgent defines that the fetched value be returned, whereas StatelessSession has historically defined a void return type.

In both cases, StatelessSession has been changed to match the EntityAgent signatures.

Query API

One of the biggest changes in Jakarta Persistence 4.0 is the better modeling of "queries" into selections (TypedQuery) and mutations (its new Statement contract), aligning closely with Hibernate’s already existing SelectionQuery and MutationQuery contracts. At the same time, it deprecated all "operation methods" from its "raw" Query contract.

Hibernate’s Query contracts implement the Jakarta Persistence ones, so changes were needed here.

  • Hibernate’s Query interface is still typed, but it now extends from JPA’s Query rather than TypedQuery.

  • Hibernate’s SelectionQuery now implements TypedQuery[1].

  • Hibernate’s MutationQuery now implements the new Statement contract[2].

Another impact is that @NamedQuery and @NamedNativeQuery (as well as Hibernate’s variants) may no longer be used to define mutation queries -

  • @NamedQuery - A SelectionQuery defined using HQL/JPQL

  • @NamedStatement - A MutationQuery defined using HQL/JPQL

  • @NamedNativeQuery - A SelectionQuery defined using native SQL

  • @NamedNativeStatement - A MutationQuery defined using native SQL

Removal of org.hibernate.transform package

The entire org.hibernate.transform package, which was deprecated since 6.0, has been deleted. This includes Transformers, AliasToBeanResultTransformer, AliasToBeanConstructorResultTransformer, AliasToEntityMapResultTransformer, and ToListResultTransformer.

Use org.hibernate.query.TupleTransformer (a @FunctionalInterface) as a direct replacement. Since it is a functional interface, lambdas can be used inline:

// Before (removed):
query.setTupleTransformer( Transformers.mapTransformer() );

// After:
query.setTupleTransformer( (tuple, aliases) -> {
    Map<String, Object> map = new HashMap<>( tuple.length );
    for ( int i = 0; i < tuple.length; i++ ) {
        if ( aliases[i] != null ) {
            map.put( aliases[i], tuple[i] );
        }
    }
    return map;
} );

For bean-based result transformation (formerly Transformers.beanTransformer()), use HQL/JPQL dynamic instantiation (select new MyDto(…​)) or a @jakarta.persistence.ConstructorResult mapping instead. A TupleTransformer lambda also works well for simple DTO projections:

// Before (removed):
query.setTupleTransformer( Transformers.beanTransformer( MyDto.class ) );

// After:
query.setTupleTransformer( (tuple, aliases) -> {
    MyDto dto = new MyDto();
    dto.setId( (Integer) tuple[0] );
    dto.setName( (String) tuple[1] );
    return dto;
} );

Query and Transaction Timeouts

Historically, both Hibernate and Jakarta Persistence have defined timeouts as integer values. Confusingly, Hibernate generally used second precision while Jakarta Persistence used millisecond precision. Starting in 3.2, Jakarta Persistence added a new Timeout class which helps alleviate this potential confusion. Originally this new Timeout type was not exposed on any API directly, so there was no impact from an API perspective. However, this has changed a bit in 4.0 where Timeout is now exposed on certain APIs. This causes a signature conflict with a few of Hibernate API methods; as part of fixing those conflicts, it was decided to change all exposures of timeout in Hibernate APIs to use Timeout as this helps clarify these confusions.

AttributeNode Subgraphs

As part of its support for "entity graphs", Hibernate’s org.hibernate.graph.AttributeNode contract extends the Jakarta Persistence jakarta.persistence.AttributeNode. Starting in version 4.0, Jakarta Persistence has "fixed" the use of raw types for the AttributeNode#getSubgraphs and AttributeNode#getKeySubgraphs methods. Hibernate has therefore needed to do the same in its org.hibernate.graph.AttributeNode.

Join and Fetch in Criteria

Jakarta Persistence has changed/fixed the type signature of methods in the Criteria API which return Join and Fetch based on the String name of attributes. This can lead to compilation issues if an application uses these methods in certain ways.

ProcedureCall and ProcedureOutputs

The API for ProcedureCall and ProcedureOutputs has been redesigned for a number of reasons, mostly -

  • Fix a long-standing bug where multiple result mappings were not handled properly.

  • Better handling of ProcedureOutputs to avoid casts and, even worse, often untyped casts. This is achieved by the new methods added to Output. See link:https://docs.jboss.org/hibernate/orm/8.0/whats-new/whats-new.html#procedure-outputs-casting.

  • Clean up leakage of various SPI contracts into these API contracts.

  • Align with changes in Jakarta Persistence 4.0.

FindMultipleOption

The four enums which are implementors of org.hibernate.FindMultipleOption have been moved as inner enums of org.hibernate.FindMultipleOption itself :

  • org.hibernate.OrderingModeorg.hibernate.FindMultipleOption.OrderingMode

  • org.hibernate.SessionCheckModeorg.hibernate.FindMultipleOption.SessionCheckMode

  • org.hibernate.RemovalsModeorg.hibernate.FindMultipleOption.RemovalsMode

  • org.hibernate.BatchSizeorg.hibernate.FindMultipleOption.BatchSize

These enums were previously incubating, except for org.hibernate.BatchSize which is now marked as deprecated.

@GenericGenerator

The @GenericGenerator annotation was deprecated in Hibernate 6.5. Instead of removing it completely, we have modernized it and reversed its deprecation. Code still using @GenericGenerator must adapt to its reworked definition.

Deprecated OptimisticLock Annotation

The venerable @OptimisticLock annotation was deprecated in favor of the new JPA-standard @ExcludedFromVersioning annotation.

Changes to Hibernate Processor

Jakarta Data and Jakarta Persistence are now more integrated, which has led to some usage changes.

Repository interface discovery

With the addition of static queries to Jakarta Persistence comes a requirement for the Jakarta Persistence provider to discover Jakarta Data repository interfaces. When Hibernate is started via a PersistenceConfiguration, discovery is impossible, and so Jakarta Data repository interfaces must be explicitly registered with the configuration:

var config = new PersistenceConfiguration("Jakarta Data Example");
// register entities
List.of(Book.class, Author.class, Publisher.class)
        .forEach(config::managedClass);
// register repository interfaces
List.of(Library.class, Bookshop.class)
        .forEach(config::managedClass);

Repository implementation instantiation

Previously, generated Jakarta Data Repository implementation classes were named with a trailing underscore. These classes are now named with a leading underscore to avoid collision with Jakarta Persistence static query methods. Thus, to directly instantiate a Jakarta Data Repository implementation, client code must now use:

Library library = new _Library(entityAgent);

This change does not affect code which obtains a repository by injection.

Changes to SPI

This section describes changes to contracts (classes, interfaces, methods, etc.) which are considered SPI.

Graph-based Flushing

Many changes were needed across various SPIs to accommodate the new Graph-based ActionQueue, focused around the new org.hibernate.action.queue.spi package which primarily defines -

  • ActionQueue

  • ActionQueueFactory

  • QueueType

  • PlanningOptions

  • graph planning/bind/decompose/meta/plan contracts

Main SPI touch points include -

  • SessionFactoryImplementor#getActionQueueFactory() exposes the configured queue factory

  • org.hibernate.engine.spi.ActionQueue was renamed to ActionQueueLegacy implementing the new org.hibernate.action.queue.spi.ActionQueue interface

  • session/event access now points at org.hibernate.action.queue.spi.ActionQueue

  • Collection SPI additions for graph decomposition

    • PersistentCollection#getRemovedEntities

    • #getAddedEntities

    • #getChangeSet

    • CollectionChangeSet

    • SnapshotIndexed

  • Persister/state-management SPI hooks for graph decomposition

  • Split org.hibernate.sql.model.MutationTarget into GraphMutationTarget and LegacyMutationTarget

  • StateManagement support for graph-aware collection/entity state changes

  • Batch split

    • Batch narrowed to lifecycle only

    • GroupedBatch (extends Batch) supports legacy "grouped statement" execution

    • SingleStatementBatch, StatementBinder, and BatchedResultChecker support single statement batching (graph)

Classmate removal

The dependency on Classmate was removed, which had a minor impact on certain SPIs. In particular, ClassmateContext was completely removed.

Raw types in JPA Bootstrap

Raw Map types were eliminated from the signatures of methods of the class org.hibernate.jpa.boot.spi.Bootstrap.

Query Memento

Hibernate uses a number of implementations of its NamedQueryMemento contract to model "named queries". Starting in version 3.2, Jakarta Persistence added the TypedQueryReference contract intended to serve the much the same goal. Hibernate’s NamedQueryMemento implementations were changed to extend from TypedQueryReference.

In version 4.0, Jakarta Persistence has added some additional methods to TypedQueryReference which now conflict with some of Hibernate’s existing methods requiring a rename. Notably, the addition of TypedQueryReference#getParameterTypes caused conflict’s with Hibernate’s NamedSqmQueryMemento#getParameterTypes. To address this conflict, we’ve renamed the NamedSqmQueryMemento method to #getAnticipatedParameterTypes.

Scanning

Jakarta Persistence clarifies that scanning is the responsibility of the container, rather than the provider. To that effect, the scanning SPI, and related ArchiveDescriptor SPI, have been rewritten. During "EE bootstrap", Hibernate no longer performs scanning. Instead, the jakarta.persistence.spi.PersistenceUnitInfo has been augmented to make it clear that the container passes in any classes discovered during scanning.

Hibernate does still provide this SPI for use by containers - Wild Fly for example takes advantage of this.

Hibernate also uses this scanning SPI when applications use HibernatePersistenceConfiguration bootstrapping and supply the root and/or non-root URLs.

Changes in Behavior

This section describes changes in behavior that applications should be aware of.

Jakarta Validation DDL influence enabled by default

Jakarta Persistence 4.0 specifies that constraints from Jakarta Validation annotations, such as @NotNull, @Size, @Digits and others, should influence the generated DDL schema. For example, a property annotated @NotNull will result in a non-nullable column.

Previously, this behavior required explicitly setting the validation mode to DDL (a Hibernate-specific extension to the JPA ValidationMode enum).

As a result:

  • org.hibernate.boot.beanvalidation.ValidationMode.DDL is deprecated for removal.

  • A new setting hibernate.tooling.schema.apply_validation_constraints has been added to SchemaToolingSettings as the way to control the influence of Jakarta Validation constraints on the generated DDL schema. Valid values are defined by ValidationConstraintDdlInfluence:

    • AUTO (default) — apply constraints if a Jakarta Validation provider is available, silently skip otherwise

    • REQUIRED — apply constraints and fail if no Jakarta Validation provider is available

    • DISABLED — do not apply constraints

  • The legacy setting hibernate.validator.apply_to_ddl is also deprecated for removal in favor of the new setting.

Graph-based Flushing

As described in What’s New and SPI Changes, 8.0 changes how Hibernate coordinates work needed for flushing. This new coordinator can often lead to different SQL or SQL being performed in different order. The legacy strategy remains temporarily available by configuration using

hibernate.flush.queue.type=legacy

Queries Returning Detached Collections

A HQL query like:

select items from Order /* each query result is a collection */
select elements(items) from Order /* each query result is a collection */
select indices(items) from Order /* each query result is a collection */

now returns a detached collection without flattening.

In previous versions of Hibernate, such queries were accepted, and due to an undocumented and unsupported behavior, resulted in an implicit join and flattening of the collection. To recover this previous undocumented behavior, the singular form of the functions may be used:

select element(items) from Order /* flatten the items collection */
select indice(items) from Order /* flatten the items collection */

Procedure Results

The behavior of Hibernate’s handling for creating a ProcedureCall / StoredProcedureQuery with multiple result set mappings has been changed to fix a long-standing bug and comply with the Jakarta Persistence specification. Hibernate now interprets the multiple mappings as one per result available from the procedure. E.g.

var call = session.createProcedureCall("the_proc",
        Region.class, Initiative.class);
var outputs = call.getOutputs();

// old approach with casts still works -
List<Region> regions = ( (ResultSetOutput<Region>) outputs.getCurrent() ).getResultList();

outputs.gotToNext();

// but consider this instead -
List<Initiative> initiatives =  outputs.getCurrent()
        .asResultSetOutput(Initiative.class)
        .getResultList();

Final fields of entities

A final field of an entity is now treated as if it were annotated @Immutable, that is, it is excluded from dirty checking and from SQL UPDATE statements. In order to eliminate the possibility of "silent" changes in behavior, attempts to modify the value of a final fields using the merge() method are rejected. If a field of an entity can actually be updated via merge(), the field must be declared non-final.

Session.get

Jakarta Persistence now defines a series of overloaded EntityHandler.get() methods as corollaries to EntityHandler.find(). These methods are defined to throw an EntityNotFoundException rather than return null. This operates quite differently from the older Hibernate Session.get() methods which

  • still returned null if no entity with that is was found

  • force initialized the entity, if there was one and it was previously uninitialized.

Applications which use(d) Session.get() should be aware of this change in behavior.

Scanning

As discussed in Scanning above, Hibernate no longer performs scanning during "EE bootstrap". It is expected by Jakarta Persistence that the container perform the scanning prior to bootstrapping Hibernate.

Remove AttributeNode from EntityGraph

Calls to EntityGraph.removeAttributeNode() and EntityGraph.removeAttributeNodes() now behave in specification compliant way - when the graph is used as a "load graph", removing an attribute "suppresses inclusion of an attribute mapped for eager fetching".

Entity Listener Callbacks

Jakarta Persistence has clarified that listener-style callback classes (@EntityListeners) may define multiple methods for a given event type -

An entity listener class may have multiple callback methods for a given type of lifecycle event, but at most one callback method for a given type of event and given parameter type.

Using <E> as a generic type for the signatures, it says -

where E is an entity class, a mapped superclass, or a supertype of the entity class or mapped superclass to which the entity listener applies. If multiple entity classes are assignable to the type E, the callback method is invoked for any such class to which the entity listener applies.

@Entity
@EntityListeners( AnimalWatcher.class )
public class Cat implements Animal { ... }

@Entity
@EntityListeners( AnimalWatcher.class )
public class Dog implements Animal { ... }

public class AnimalWatcher {
    @PostInsert
    public void postInsert(Animal animal) { ... }

    @PostInsert
    public void postInsert(Cat animal) { ... }

    @PostInsert
    public void postInsert(Dog animal) { ... }
}

Be aware that creating a Cat, with the above model, will result in calls to both postInsert(Animal) and postInsert(Cat); and that creating a Dog, both postInsert(Animal) and postInsert(Dog).

Discriminator-Based Multi-Tenancy and Row-Level Security

On databases with built-in support for row-level security, Hibernate now uses row-level security to enforce the visibility rules implied by discriminator-based multi-tenancy. This requires specific DDL which the schema management tooling is responsible for generating.

To recover the previous behavior, where discriminator-based multi-tenancy rules were not enforced by the database, set the configuration property hibernate.multi_tenant.rls_enabled=false.

Session Reentrancy

It has always been illegal to call the Hibernate session from an Interceptor or JPA entity lifecycle callback, but Hibernate has never previously enforced this, opening the door to undefined behavior in programs which did not respect this restriction.

Hibernate now throws IllegalStateException if the session is accessed in an unsafe way from a callback.

EntityExistsException and Exceptions from StatelessSession

JPA 4 specifies that EntityExistsException should be thrown on any sort of unique key violation that occurs while inserting an entity, which means that Hibernate now throws this exception type in some cases where ConstraintViolationException was previously thrown.

After the introduction of EntityAgent, the Hibernate StatelessSession now throws the exception types mandated by the JPA specification. Previously, it threw native exception types.

CUBRID dialect

CUBRIDDialect now requires CUBRID 10.2 or later, the oldest version still supported by the vendor, and declares several capability flags explicitly for 10.2. Two of these are visible at runtime.

getNullOrdering() is now SMALLEST, which matches CUBRID’s native ordering (NULL first when ascending and last when descending). Queries that relied on the previous inherited default may order NULL values differently.

Pessimistic locking now produces a FOR UPDATE clause. The dialect previously returned an empty lock string, so PESSIMISTIC_READ and PESSIMISTIC_WRITE had no effect.

Changes in XSD

This section describes changes in XML Schema Descriptors

Table and Column Comments

Previous versions of the mapping.xsd defined table and column comments (for schema export) using XSD attributes. This was a decision made at the time to facilitate users migrating from hbm.xml mapping format. In the intervening period, Jakarta Persistence has also added comments to its table and column XSD types, but using dedicated element.

We now align with the Jakarta Persistence approach of using elements. This will require changes any mapping.xml documents which define table or column comments. E.g.,

<entity ...>
    <table ... comment="some comment"/>
</entity>

would need to be changed to

<entity ...>
    <table>
         <comment>some comment</comment>
    </table>
</entity>

Removal of orphan-removal from <many-to-many>

The orphan-removal attribute has been removed from the <many-to-many> element in the XML mapping XSD.

Although previous versions of the schema allowed specifying orphan-removal="true" on a <many-to-many> mapping, this attribute was never processed during boot. Orphan removal behavior was silently ignored for many-to-many associations. The attribute has been removed from the XSD to reflect the actual supported behavior.

Any mapping.xml documents using this attribute:

<many-to-many orphan-removal="true" ... />

should remove the orphan-removal attribute, as it had no effect.

Removal of proxy and polymorphism from Entity Mappings

TThe proxy and polymorphism XML mapping attributes have been removed from the entity mapping XSD.

This change follows the removal of the corresponding @Proxy and @Polymorphism annotations in Hibernate ORM 7.0.0.Beta1.

Both elements have been removed from the mapping-8.0.xsd schema along with the polymorphism-type simple type definition.

Any mapping.xml documents using these elements:

<entity ...>
    <proxy>com.example.MyEntityProxy</proxy>
    <polymorphism>EXPLICIT</polymorphism>
</entity>

should remove them, as they are no longer supported.

Migration of Spanner PostgreSQL Dialect to core

The SpannerPostgreSQLDialect, which was previously part of the hibernate-community-dialects artifact under the package name org.hibernate.community.dialect, has been migrated to the hibernate-core artifact and package name org.hibernate.dialect.

Users currently using the community dialect must update their dialect configuration to use org.hibernate.dialect.SpannerPostgreSQLDialect (or rely on Hibernate’s automatic dialect resolution).

Changes to DDL generation

This section describes changes to DDL generated by the schema export tooling. Such changes typically do not impact programs using a relational schema managed externally to Hibernate.

Optional: Migrating from Hibernate Envers to core @Audited

This migration is entirely optional. Hibernate Envers continues to work as before in 8.0. However, if you wish to take advantage of the new core @Audited functionality, this section describes the steps involved.

The new core audit support provides very similar functionality to Envers with a simpler programming model: you can use standard Session APIs (and even HQL queries!) within a temporal session opened via atChangeset(), or the more specialized AuditLog functionality, to access the audit logs of your entities.

Annotation changes

Envers Core

@org.hibernate.envers.Audited

@org.hibernate.annotations.Audited

@org.hibernate.envers.NotAudited

@org.hibernate.annotations.Audited.Excluded

@org.hibernate.envers.RevisionEntity

@org.hibernate.annotations.Changelog

@org.hibernate.envers.RevisionNumber

@org.hibernate.annotations.Changelog.ChangesetId

@org.hibernate.envers.RevisionTimestamp

@org.hibernate.annotations.Changelog.Timestamp

RevisionListener

org.hibernate.audit.ChangesetListener (method: newChangeset())

EntityTrackingRevisionListener

org.hibernate.audit.EntityTrackingChangesetListener

Map your REVINFO table

Define a @Changelog entity that maps to the existing REVINFO table (or use the built-in DefaultChangelog):

@Entity
@Table(name = "REVINFO")
@Changelog(listener = MyChangesetListener.class)
public class MyRevision extends ChangelogMapping { ... }

API changes

Envers Core

AuditReader.find(Foo.class, id, rev)

auditLog.find(Foo.class, id, rev) or sf.withOptions().atChangeset(rev).openSession().find(Foo.class, id)

reader.getRevisions(Foo.class, id)

auditLog.getChangesets(Foo.class, id)

reader.getRevisionDate(rev)

auditLog.getChangesetTimestamp(rev)

reader.findRevision(RevInfo.class, rev)

auditLog.findChangeset(RevInfo.class, rev)

reader.createQuery().forEntitiesAtRevision(…​)

HQL in atChangeset() session

reader.createQuery().forRevisionsOfEntity(…​)

auditLog.getHistory(Foo.class, id)

Custom AuditQuery criteria

HQL with changesetId() / modificationType() functions

Schema compatibility

No DDL changes are needed. The default REV/REVTYPE column names and encoding match between Envers and core. The REVINFO table rows continue the same sequence.

Changes in Dependencies

This section describes changes to dependencies used by Hibernate ORM.

Micrometer dependency is now provided

The hibernate-micrometer module no longer pulls io.micrometer:micrometer-core as a transitive runtime dependency. Applications using this module must now explicitly add io.micrometer:micrometer-core to their classpath.


1. Unfortunately that means `SelectionQuery` now inherits the `executeUpdate()` method from JPA’s `Query`, but as mentioned that method is deprecated.
2. Unfortunately that means `MutationQuery` now inherits `getResultList()`, and other such methods from JPA’s `Query`, but as mentioned these methods are all deprecated.