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.

Managed package and module descriptors

Register package metadata using MetadataSources.addPackageDescriptor(String) or addPackageDescriptor(Package), and module metadata using addModuleDescriptor(String) or addModuleDescriptor(Module). The former addPackage and addModule methods remain as deprecated forwarding aliases.

Explicit package registration now requires a resolvable package-info.class. Bootstrap fails if the descriptor is missing, including for an existing package without package-info and when using the deprecated addPackage aliases. Remove registrations that do not identify a descriptor, or supply the intended descriptor. Implicit package annotation lookup from a class continues to tolerate absence.

A class input may no longer name package-info or module-info. For example, replace <class>com.acme.package-info</class> with <package-descriptor>com.acme</package-descriptor> in persistence.xml. This check also applies to older XML documents upgraded by Hibernate’s reader. Use JPA’s managed package/module descriptor APIs for programmatic configuration and its separate explicit/complete descriptor lists for container integrations. Registering a descriptor contributes its annotations without enrolling classes in its package or module.

Scanner results contain separate class, package, module, and mapping-resource inventories. The Jandex scanner uses selected archives as the candidate inventory, including when a prebuilt index is supplied. The supplied index may contribute annotation definitions, but cannot enroll resources outside those archives. jpaScan() excludes root discovery when exclude-unlisted-classes is true or omitted; listed JARs and the root standard mapping resource are still considered.

Filter definition API

SessionFactory.getFilterDefinition(String) and Filter.getFilterDefinition() now return the API interface org.hibernate.engine.FilterDefinition. Their deprecations have been removed. The interface exposes the filter name, parameter names, default condition, automatic enablement, and load-by-key flag. The existing SPI class implements this interface, so the returned objects and the behavior for unknown filter names are unchanged.

The changed return types are a binary compatibility break: callers compiled against the old API declarations must be recompiled. Source code assigning the result to org.hibernate.engine.spi.FilterDefinition must use the API interface or migrate to SPI access if it needs JDBC mappings, parameter resolvers, or argument processing. See Filter definition API/SPI split.

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.OrderingMode → org.hibernate.FindMultipleOption.OrderingMode

  • org.hibernate.SessionCheckMode → org.hibernate.FindMultipleOption.SessionCheckMode

  • org.hibernate.RemovalsMode → org.hibernate.FindMultipleOption.RemovalsMode

  • org.hibernate.BatchSize → org.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 default list semantics setting

The hibernate.mapping.default_list_semantics setting and MappingSettings.DEFAULT_LIST_SEMANTICS constant are deprecated for removal in ORM 9.0. Use @DefaultListSemantics on a package or module instead.

For example, replace hibernate.mapping.default_list_semantics=LIST with the following declaration in each applicable package-info.java, or with the equivalent annotation on module-info.java:

@DefaultListSemantics(LIST)
package org.example.domain;

import org.hibernate.annotations.DefaultListSemantics;
import static org.hibernate.annotations.DefaultListSemantics.Classification.LIST;

Use BAG instead of LIST to retain bag semantics in an annotated scope. Package defaults override module defaults and do not extend to subpackages. Inherited attributes use the package and module of their declaring type; embedded attributes use those of the embeddable. When migrating a persistence-unit-wide setting, cover all applicable declaring packages or modules before removing the setting.

During the transition, the setting remains a fallback where neither annotation applies. Explicit use logs a deprecation warning even when an annotation overrides its effect. With no annotation or configured default, Hibernate continues to use BAG.

This replacement preserves the existing classification rules: explicit mappings take precedence, and lists with retrieval ordering or unowned to-many associations do not acquire an implicit order column. See the User Guide for the complete decision tree.

Deprecated OptimisticLock Annotation

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

Fixed delimited-identifiers in XML mappings

Previous versions of Hibernate did not respect the <delimited-identifiers> element of orm.xml. This is now fixed.

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.

Security annotations on Jakarta Data repositories

The annotation processor now propagates security annotations from the jakarta.annotation.security package (@DeclareRoles, @DenyAll, @PermitAll, @RolesAllowed, @RunAs) from repository interfaces to generated implementations. Previously, only @InterceptorBinding annotations were propagated.

Changes to SPI

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

Property value access

Hibernate now integrates Hibernate Accessor for property value access. PropertyAccess implementations must implement the new getPropertyValueAccessor() method, returning a PropertyValueAccessor for the mapped attribute. There is no default implementation of this method; custom implementations must be updated and recompiled.

Use the appropriate PropertyValueAccessor factory for the access pattern, such as standard() for a reader/writer pair or map() for map-backed properties. The existing getGetter() and getSetter() methods remain part of the contract, but implementing those methods alone is no longer sufficient. Integrations must preserve their property’s read, write, and insert-time access semantics through the new accessor. PropertyValueAccessor is currently incubating.

Custom dialect providers

Dialect extension contracts have been reorganized and explicitly classified using @SPI. Custom dialects must update imports and overrides to the current contracts, including the focused org.hibernate.dialect.lock.spi, org.hibernate.dialect.pagination.spi, and org.hibernate.dialect.mutation.spi packages where applicable. Use supported strategy contracts and factories instead of implementations in internal packages.

Extend Dialect or another type explicitly classified with SPI.Role.IMPLEMENT. A built-in dialect being available for selection does not by itself make it a supported subclass base. The USE, IMPLEMENT, and SUPPLY roles are independent, and compatibility expectations for non-incubating SPIs apply within one X.Y release family.

See the Dialect Provider Guide for supported extension points, migration examples, and provider validation tooling.

Removal of custom cascade implementations

Custom implementations of CascadeStyle and CascadingAction are no longer "supported". They were never well-supported anyway. These contracts, now in org.hibernate.cascade.spi, are internal strategies rather than extension points. The CascadeStyles.registerCascadeStyle() registration method has been removed, and the cascade coordinator rejects custom actions.

Integrations must remove custom cascade registrations and use Hibernate’s built-in cascade styles. There is no replacement extension contract for custom cascade styles or actions; application-specific propagation of persistence operations must be implemented explicitly.

Client enhancement

Enhancer.enhanceClient(String, byte[]) performs client field-access rewriting separately from managed-class enhancement. Its default implementation reports unsupported client enhancement, so existing enhancer implementations continue to work when it is unused. HibernatePersistenceProvider.getClientClassTransformer(PersistenceUnitInfo, Map) exposes an independent Jakarta transformer for containers. Targets must already have compatible managed enhancement from tooling, or the container must arrange it before defining them. When both transformations apply to a class, managed enhancement precedes client enhancement. A client may reference another class scheduled for later managed enhancement. Runtime client enhancement can be used with runtime managed enhancement disabled when tooling has already enhanced the targets. Requesting the client transformer does not register either transformer.

Gradle, Maven, and Ant accept enableClientEnhancement, defaulting to false. enableExtendedEnhancement remains a deprecated alias. When both are set, the new name wins, including explicit false. Client classes no longer receive a managed-enhancement marker simply because client enhancement was requested. Targets must be scheduled for managed enhancement or already carry compatible enhancement; known skipped or incompatible targets fail during enhancement instead of producing unavailable accessor calls. XML mapping recognition is unchanged, and reflection and method-handle accesses are outside the client transformation.

Enhancement model, options, and environment

EnhancementContext, DefaultEnhancementContext, and EnhancementContextWrapper have been replaced by independent EnhancementModel, EnhancementOptions, and EnhancementEnvironment contracts. DefaultEnhancementModel supplies the existing annotation-based interpretation. Discovery and metadata belong to an EnhancementSession, not to the model or options.

BytecodeProvider implementations must replace getEnhancer(context) with createEnhancementSession(model, environment). There is no compatibility adapter. Direct EnhancerImpl construction is replaced by session creation as well:

try (var session = provider.createEnhancementSession(
        model, EnhancementEnvironment.forClassLoader(loader))) {
    var enhancer = session.createEnhancer(
            EnhancementOptions.of(dirtyTracking, lazyInitialization, associationManagement));
    session.discoverTypes(className, originalBytes);
    byte[] enhanced = enhancer.enhance(className, originalBytes);
}

Environments resolve bytecode without defining application classes and may use a custom byte source instead of a loader. The Byte Buddy provider also accepts a borrowed EnhancerClassLocator when creating a session. Sessions do not close borrowed environments, locators, or class loaders.

Call invalidateMetadata() between passes which replace class resources. This clears bytecode and getter caches while retaining discovered persistence classifications. Close or invalidate only when no session operations are active. Closing a session makes all of its enhancers unusable.

The paired JPA transformer factories share discovery and metadata when called on the same HibernatePersistenceProvider and PersistenceUnitInfo instances, for the same defining loader and bytecode-provider instance. Each transformer retains its own enhancer and generation options. The PersistenceUnitInfo.addTransformer bootstrap path remains independent.

Filter definition API/SPI split

org.hibernate.engine.spi.FilterDefinition remains SPI and now implements the API interface org.hibernate.engine.FilterDefinition. Its constructors, JDBC parameter mappings, managed parameter resolvers, and argument processing remain unchanged. The public API getters no longer return the SPI class.

Integrations requiring the SPI definition should use:

sessionFactory.unwrap(SessionFactoryImplementor.class)
        .getFilterDefinition(filterName);

SessionFactoryImplementor is in org.hibernate.engine.spi and retains its SPI return type. Internal FilterImpl access also retains its covariant SPI return; FilterImpl is not a supported provider access path. For an enabled Filter, use its name to look up the SPI definition through SessionFactoryImplementor.

SQL AST and translator changes

The central SQL AST and translator SPIs are no longer incubating, including the translator factories and typed requests, JdbcParameterFactory, and the JdbcOperations builders. Their supported SPI roles now carry the normal compatibility expectations within an X.Y release family. No signatures or provider implementation requirements change as part of this stabilization.

QueryTransformer, SqlAstTranslator.renderNamedSetReturningFunction(), and SetReturningFunctionType remain incubating. Separate execution, mapping-model mutation, and result-processing contracts retain their existing lifecycle status.

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.

ActionQueue speculative-flush checkpoints

The incubating ActionQueue.clearFromFlushNeededCheck(int) method has been replaced by an opaque checkpoint and restore contract:

ActionQueueCheckpoint checkpoint();
void restore(ActionQueueCheckpoint checkpoint);

An ActionQueueCheckpoint belongs to the queue instance which created it and does not expose the queue’s internal list or graph representation. Queue implementations must preserve all work registered before the checkpoint. Work registered afterward is retained when it represents durable persistence-context state, including entity inserts, entity deletes, orphan removals, and orphan collection removals. Flush-derived entity updates and ordinary collection work may be discarded only while their source state remains intact and can reproduce equivalent work during a later required flush.

Collection mutation registration now uses the queue-neutral addCollectionMutation(CollectionMutationInput) method. The collection-specific addAction(CollectionRecreateAction), addAction(CollectionRemoveAction), addAction(CollectionUpdateAction), and addAction(QueuedOperationCollectionAction) overloads have been removed from the incubating ActionQueue SPI. Queue implementations may lower the semantic input to these legacy action forms internally, but they are no longer the common registration vocabulary.

Collection mutation interpretation, including semantics-specific delta production, is now associated with CollectionSemantics through the new default getCollectionMutationInterpreter() method. Custom semantics remain source compatible and use the conservative compatibility interpreter unless they override this association. PersistentCollection likewise has new default methods for exposing queued commands, a mutation generation, and the post-snapshot initialization step.

Custom collection wrappers which apply queued commands during afterInitialize() should instead retain them until afterInitializationSnapshot(). Hibernate captures the database-loaded state between these calls and then applies and clears queued commands, ensuring that an initialized collection has no queued commands while its snapshot still represents the persisted comparison baseline. Wrappers derived from AbstractPersistentCollection inherit this sequencing automatically.

SessionLazyDelegator initialization

The SessionLazyDelegator class has been made abstract, and its constructor that accepted a Supplier<Session> removed, in order to align it with how the new StatelessSessionLazyDelegator` is implemented. Consumers of this class must extend it and implement the abstract delegate() method, thus providing the required Session to it.

Interceptor classification

Interceptor, previously considered an API simply because it was not yet classified, is classified as an SPI with the IMPLEMENT and SUPPLY roles. Implementing its callbacks and supplying an instance to Hibernate remain supported. Hibernate continues to invoke them during session operations.

The interceptor registration methods on SessionBuilder, StatelessSessionBuilder, their shared-session variants, CommonBuilder, CommonSharedBuilder, SessionFactoryBuilder, and Configuration are now SPI supply points instead of API.

These classification changes do not change Java signatures or runtime behavior. Existing implementations and registration calls require no source changes. The affected contracts now carry the SPI compatibility promise within an X.Y release family, instead of the API promise across a major version.

Removed SessionFactoryOptions.getInterceptor() and getStatelessInterceptorImplementorSupplier()

The SessionFactoryOptions.getInterceptor() and getStatelessInterceptorImplementorSupplier() methods have been removed, along with their declarations on AbstractDelegatingSessionFactoryOptions. Both methods forced immediate resolution of the configured Interceptor, which bypassed CDI-managed bean lifecycles and could no longer reflect a session factory’s interceptor configuration now that interceptors may be resolved as CDI beans.

Use SessionFactoryOptions.getConfiguredInterceptor() instead, which reports how the interceptor was configured (instance, global, session-scoped, or supplied) without resolving any bean. To obtain the actual factory-level interceptor, use SessionFactoryImplementor.getInterceptorStrategy().getFactoryInterceptorBean(), which returns a ManagedBean<? extends Interceptor> resolved lazily on first access to getBeanInstance().

// Before:
Interceptor interceptor = sessionFactory.getSessionFactoryOptions().getInterceptor();

// After:
var bean = sessionFactoryImplementor.getInterceptorStrategy().getFactoryInterceptorBean();
Interceptor interceptor = bean == null ? null : bean.getBeanInstance();

Initializers for filtered associations

EntityInitializer now has a default getFilteredAssociationKey(RowProcessingState) method. It exposes the stored association key when a restriction hides the target. The default returns null, so existing implementations remain source compatible. Custom entity initializers which represent restricted targets as null must supply this information during hydration to participate in the stored-reference preservation described below. EmbeddableInitializer.collectFilteredAssociations() contributes hidden references directly to the owning entity’s state using the supplied shared mapping metadata. It must return the supplied state unchanged when there is no additional evidence. Custom embeddable initializers must implement this hook when they contain restricted associations. Custom entity persisters must supply the shared metadata via EntityPersister.getFilteredAssociationMapping(). Hibernate’s standard initializers and entity persisters manage this automatically.

TableUpdateBuilder implementations must implement setColumnInclusion() and setRowKnownToExist(). The first limits assignments, including generated assignments, to the permitted columns. The second requires an update of an existing row instead of an optional-row insert or upsert when stored values have been omitted. Standard builders implement both operations. Builders extending AbstractTableUpdateBuilder inherit column inclusion and can consult isRowKnownToExist() when choosing the mutation form.

Internal subclasses overriding UpdateCoordinatorStandard.performUpdate() or analyzeUpdateValues() must forward the additional FilteredAssociationMutation argument. performUpdate() also receives a boolean indicating eligibility for the update variant cache. Passing the same mutation view through the operation avoids reconstructing it or looking up the persistence-context entry twice.

AbstractCollectionPersister is an internal implementation class. Its protected applyWhereFragments() method which accepts a TableGroup now also accepts a boolean useQualifier parameter immediately before SqlAstCreationState. Custom collection persisters overriding this method must update their signature and forward this argument when calling the superclass implementation.

Changes in Behavior

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

Access type for embeddables

The fixes for HHH-13537 and HHH-21017 correct how embeddables inherit their access type.

Embeddable elements of an @ElementCollection and embeddable map keys now inherit the access type of the class declaring the collection attribute, instead of the access type of the owning entity’s identifier. An attribute-level @Access override on the identifier no longer changes the access type inherited by these embeddables. This also applies when the collection is declared by a mapped superclass or a containing embeddable, and the inherited access type propagates to nested embeddables.

For example, if an entity uses field access but overrides its identifier to use property access, its embeddable collection elements now use field access by default. Previously, Hibernate incorrectly used property access, potentially causing bootstrap failures when the embeddable had no accessor methods.

Additionally, an attribute-level @Access override on an @Embedded, @EmbeddedId, or collection attribute now controls only access to that attribute. The embedded state inherits the containing class’s access type independently of the override. This applies to singular embeddables, collection elements, and embeddable map keys in both element collections and relationship maps.

For example, a field-accessed entity may use @Access(AccessType.PROPERTY) on the getter of an embedded attribute or element collection. Hibernate uses that getter to access the attribute, but uses field access for the embeddable’s state by default. Previously, the attribute override also caused the embeddable to use property access.

Applications that relied on the previous behavior should declare the intended access type explicitly on the embeddable using @Access(AccessType.PROPERTY) or @Access(AccessType.FIELD). An explicit access type on the embeddable continues to take precedence. If the same embeddable is used in multiple contexts that require different access types, review those usages before applying a class-level override.

This change aligns with the expected Jakarta Persistence behavior.

Bootstrap information logging

The following bootstrap messages now use the dedicated org.hibernate.orm.info logging category:

  • HHH000001: Hibernate ORM core version …​, previously logged under org.hibernate.orm.core.

  • HHH10001005: Database info: …​, previously logged under org.hibernate.orm.connections.pooling.

Both messages retain their existing message IDs and INFO level. Update any logging configuration or filters targeting these messages to use org.hibernate.orm.info. This category allows applications and containers to suppress bootstrap information independently of other core and connection-pooling messages.

Logging for unscheduled deletions

Persisting an entity currently scheduled for deletion unschedules that deletion per the Jakarta Persistence specification. Hibernate now logs “Unscheduling entity deletion {details}” at DEBUG level under the org.hibernate.orm.event.unschedule-deletion category, isolating it to make it easier to enable logging for that single message.

Persistence behavior is unchanged.

Disabling XML mappings during native bootstrap

When hibernate.xml_mapping_enabled=false, native bootstrap now skips access to mapping XML resources and JAXB binding. Previously, MetadataSources and Configuration still read and bound mapping XML, then excluded the bindings from metamodel building. Missing or malformed mapping XML therefore no longer causes bootstrap to fail when XML mapping processing is disabled.

Set this option before registering XML mappings, for example before calling addResource() or addFile(). Native registration uses the setting available at the time of the call. Disabling XML later does not undo binding that has already occurred, and mappings skipped while disabled are not replayed if XML is enabled later.

XML mapping resource, file, and jar references in hibernate.cfg.xml are also skipped when XML mappings are disabled, for both native and JPA bootstrap. The configuration document itself is still read, and its class and package mapping references still apply.

Legacy JPA connection aliases during settings normalization

Native bootstrap now recognizes legacy javax.persistence. connection settings when normalizing settings for the standard service registry. Previously, this normalization supplied Hibernate connection settings from jakarta.persistence. aliases only; JPA bootstrap already recognized the legacy aliases.

The newly recognized aliases are:

  • javax.persistence.jdbc.url

  • javax.persistence.jdbc.driver

  • javax.persistence.jdbc.user

  • javax.persistence.jdbc.password

  • javax.persistence.nonJtaDataSource

  • javax.persistence.jtaDataSource

An existing Hibernate connection setting takes precedence over its Jakarta alias, which takes precedence over its legacy javax.persistence.* alias. For the datasource setting, all Jakarta aliases are considered before legacy aliases; within each namespace, the non-JTA datasource takes precedence over the JTA datasource. Existing JPA bootstrap source precedence is unchanged.

Review native bootstrap configuration for stale legacy connection settings, which may now take effect when no corresponding Hibernate or Jakarta setting is present. Prefer the jakarta.persistence.* names when migrating legacy configuration.

Query and persistence correctness fixes

The following fixes change behavior applications may observe after upgrading:

  • Concurrent StatelessSession.upsert() and upsertMultiple() operations now handle insert races instead of potentially leaving one operation’s changes unapplied.

  • On H2, Oracle, and SQL Server, HQL insert …​ on conflict (…​) do nothing with explicit conflict columns now uses MERGE emulation instead of silently dropping the conflict clause. Applications should no longer expect a unique constraint violation for the specified conflict.

  • With enhanced dirty tracking and @DynamicUpdate, changes to scalar attributes are now included in updates when nested embeddables are also dirty. Previously, some scalar changes could be silently omitted depending on attribute ordering.

Direct JDBC handling of java.time values

The default value of hibernate.type.java_time_use_direct_jdbc is now true. Previously, Hibernate converted java.time values to legacy java.sql representations before passing them to JDBC. Hibernate now uses java.time values directly where the configured dialect reports support for the Java type. For unsupported types, Hibernate falls back to the legacy JDBC representation and logs the driver/database limitation.

Applications should verify temporal value handling with their JDBC driver when upgrading, particularly where they rely on the previous conversion behavior. Set hibernate.type.java_time_use_direct_jdbc=false to retain the legacy behavior.

Removal of lock cascading

The legacy lock cascade style and the CascadeStyles.LOCK and CascadingActions.LOCK constants have been removed. Legacy hbm.xml mappings that specify cascade="lock", either alone or in a comma-separated list, now fail with an unsupported cascade style error.

Remove lock from these cascade declarations, omitting the declaration if no other cascade styles remain. Applications that need to lock associated entities should explicitly request the required lock on each entity instead of relying on lock cascading or use JPA "extended locking".

Boot-time warning for final persistent fields

Hibernate now warns on boot when it detects persistent fields declared final in entity or embeddable classes that will need to be set through reflection. Reflective mutation of final fields is deprecated starting with JDK 26 and will be denied in a future JDK version.

To resolve these warnings:

  • Enable bytecode enhancement, which automatically removes the final modifier from persistent fields, or

  • remove the final modifier from the persistent field declarations, or

  • for @IdClass types, annotate a constructor with @Instantiator to use constructor-based instantiation (HHH-20542), or

  • for @IdClass types, switch to a Java record.

The behavior of this check is controlled by the configuration property hibernate.mapping.final_persistent_fields, which accepts the values warn (the default), error (fail on boot), and ignore (suppress the check).

See the User Guide for details.

Filters and SQL restrictions on to-one associations

Hibernate now applies a target entity’s @SQLRestriction, and enabled filters declared with @FilterDef(applyToLoadByKey = true), consistently when fetching @ManyToOne and @OneToOne associations. This includes eager and lazy fetching, entity queries, fetch joins, find(), and entity graphs. Previously, a SQL restriction could hide a join-fetched target while leaving a lazily fetched target visible. An applicable filter could instead cause EntityFilterException during eager fetching or an EntityNotFoundException when initializing a lazy reference.

A target excluded by an applicable restriction is now represented as null. Applications must handle this null value instead of relying on those exceptions. This is also possible for an association declared optional = false or mapped to a non-nullable foreign key. The Java value describes the visible association, while the database may still contain a non-null reference. An explicit inner fetch join still excludes an owner whose target does not satisfy the restriction. Use a left fetch join to retain that owner with a null association.

The default value of applyToLoadByKey remains false. A filter declared on the target entity does not begin affecting loading by key merely because it is enabled for entity queries. @SQLRestriction remains unconditional and cannot be disabled. The change does not extend to @Any fetching, which may still throw EntityFilterException.

Restrictions declared on a to-one association

@SQLRestriction and @Filter may now be declared directly on a @ManyToOne or @OneToOne field or getter. This includes associations mapped using @JoinTable and inverse one-to-one associations. The predicate restricts the target entity through that particular association. It is combined using AND with the target entity’s applicable restrictions and with other enabled filters on the association. @Filter supports the existing default conditions, parameters, and @SqlFragmentAlias declarations. @SQLRestriction qualifies mapped columns from target superclass and secondary tables. Filters referring to other target tables may name those tables explicitly using @SqlFragmentAlias.

A filter declared directly on the association applies whenever it is enabled, even when its @FilterDef uses the default applyToLoadByKey = false. That setting continues to govern filters declared on the target entity. These association annotations were previously ignored.

Visibility is specific to the association. The same target may be null through a restricted association and visible through another association or a direct find(). A target already present in the persistence context or second-level cache does not bypass the association predicate. The existing rules for hidden references, merge, orphan removal, and changing filters also apply to these annotations. The predicates constrain loading and do not validate newly assigned targets or reinterpret loaded fields.

For native results, Hibernate checks the association predicate with a separate target-key query before resolving the entity. A hidden result does not mark the target entity itself as missing. Static visibility queries are reused at mapping scope. With active filters, the result-processing state reuses the latest visibility plan for each association and key mode. Changes to enabled filters or to parameter values captured by the plan cause it to be rebuilt, including changes made while scrolling. Plans and parameter snapshots are retained only for that result set. The visibility query still executes for each reference requiring a check. No additional kind of per-entity persistence-context state is required.

These annotations restrict target rows. They do not add support for @SQLJoinTableRestriction or @FilterJoinTable on to-one associations.

Interaction with @NotFound

@NotFound(IGNORE) continues to represent a missing target as null and to require eager resolution of a to-one association. When an applicable filter or @SQLRestriction is present, Hibernate cannot distinguish a target that does not exist from one excluded by the restriction. In either case, a non-null stored foreign key is preserved by unrelated updates, including its join-table row. This also applies to @NotFound(EXCEPTION) under an applicable exclusion. Hibernate does not issue an additional query to establish whether the physical target exists.

Without an applicable exclusion, @NotFound(EXCEPTION) still throws for a dangling foreign key. @NotFound(IGNORE) retains its existing behavior in that case: the missing target becomes null, and no hidden-reference state is recorded. Consequently, an ordinary static update may write null over that dangling foreign key. A declared but disabled filter is not an active exclusion.

Stored references and updates

For an owning to-one association, Hibernate now distinguishes an absent reference from a reference whose target was filtered out, even though both are represented as null in Java. The distinction is recorded in the persistence context while hydrating the owner. The stored key is retained only for mappings that require it during mutation, as described below. It applies both to foreign keys in the owner table and to associations mapped using @JoinTable, including associations inside embeddables and associations using composite or non-primary unique keys.

The resulting update rules are:

Reference when loaded Value at flush Result

Absent

null

The reference remains absent. An unrelated update does not create a join-table row.

Visible target

null

The reference is cleared according to the mapping.

Filtered target

null

The stored reference is preserved, including its join-table row.

Absent, visible, or filtered target

A newly assigned non-null target

The association is set to the assigned target according to the mapping.

In particular, calling a setter with null when the association already appears null does not clear a hidden reference. Neither does assigning a replacement and then assigning null again before flushing. Once a non-null replacement has been flushed, subsequently assigning null clears that replacement normally. For example, given an owner whose stored target_id is 2 and whose target is hidden:

assert owner.getTarget() == null;
owner.setTarget(null);
owner.setName("changed");
session.flush(); // target_id is still 2

owner.setTarget(visibleReplacement);
session.flush(); // target_id now identifies visibleReplacement
owner.setTarget(null);
session.flush(); // the replacement reference is cleared

These rules apply to ordinary updates without @DynamicUpdate, as well as dynamic updates and @SQLUpdate. Hibernate may generate a dynamic update to omit a hidden foreign key even when the entity does not declare @DynamicUpdate. The omission is per column, so changing another field of an embeddable still updates that field. Custom update SQL retains its declared parameter structure and receives the stored key when binding a hidden association. Attribute-based optimistic locking retains the original key for its comparison predicates. Update nullability checks recognize a hidden reference as physically present without treating the Java null as a database null.

For entities with no custom update SQL on any mapped table and without ALL or DIRTY optimistic locking, Hibernate retains only a bitmap. Up to 64 potentially restricted associations use one word. Larger mappings use a word array. Nested embeddables share the owner’s bitmap. There is no allocation per hidden key or containing embeddable, and entities that never load a hidden reference require no additional entry state. Other mappings retain the keys needed for custom SQL parameters or optimistic-lock predicates. Successful replacement clears the corresponding state. An empty extension may remain attached for reuse.

Generated update operations for otherwise-static mappings are reused across sessions through a lazily allocated, bounded cache at persister scope. The cache targets 32 variants and contains only SQL structure, not entity values or persistence-context state. Its keys distinguish omitted associations, assignments, and affected tables. Identifiers, versions, and new values are bound separately for every execution. Filter parameter values are not part of the update cache key: the recorded hidden references determine which columns must be preserved. This cache applies only when hidden-reference preservation is the reason for generating an update variant. Restricted updates that also use @DynamicUpdate, custom update SQL, ALL or DIRTY optimistic locking, update generators, uninitialized lazy properties, or other requirements for dynamic mutation continue through the existing uncached path. JDBC batching remains subject to the usual statement compatibility rules.

Version checks and increments are preserved when Hibernate generates these updates, including changes confined to a join-table row.

A hidden nonnull foreign key also establishes that its join-table row exists. An unchanged hidden reference preserves that row. If other columns of the row change, Hibernate updates them without writing the hidden foreign-key columns and without attempting an insert with missing key values. This applies regardless of whether the dialect otherwise uses native upserts or upsert emulation. Deleting the owner still removes its join-table row.

Merge and orphan removal

Merging detached state assumes that the source was loaded under the same enabled filters and parameter values as the managed merge target. This requirement also applies when the merge target is already present in the persistence context. Hibernate does not validate this precondition. Under these conditions, a null association in the source preserves a hidden reference in the target, while it clears a reference which was visible when the target was loaded. Detached entities need no additional metadata. Hibernate obtains the distinction from the managed target.

With orphanRemoval = true, a filtered-out target is preserved, including when a new target replaces it. Leaving the association null, assigning null to it again, or removing the owner does not orphan-remove that hidden target. Removing or replacing a visible target continues to apply the usual orphan-removal rules.

Changing an enabled filter or its parameters does not reinterpret association state already loaded in the persistence context. To clear a reference hidden by a filter, first make it visible and refresh or reload the owner under those conditions, then assign null. Disabling a filter alone is insufficient. There is no corresponding switch for @SQLRestriction. The stored-reference information survives refresh under the same restrictions, a change from read-only to modifiable state, and serialization of the session. The serialized persistence-context format has changed to include this information. Serialized sessions from earlier Hibernate versions must not be deserialized with this version. Applications that retain serialized sessions across an upgrade must discard them and reload their entities.

Query results, generated SQL, and caching

Navigating to an association’s identifier, for example where owner.target.id = :id, now retains the target join when needed to apply an applicable enabled filter or SQL restriction. Merely declaring a filter does not disable the foreign-key optimization while that filter is disabled. Implicit path joins retain their usual inner-join semantics when a join is required. An explicit left join still retains owners whose targets are excluded. Use the HQL fk() function, for example where fk(owner.target) = :id, when the intention is to test the stored foreign key independently of whether the target is visible.

Loading a lazy association may now add a left join and select the target identifier to determine visibility without initializing the target entity. The owner query also selects the physical foreign key to distinguish a hidden reference from an absent reference. Ordinary mappings discard this key after recording the distinction. Applications which inspect generated SQL or count joins should account for these changes. For native queries and stored procedure results, Hibernate cannot add these selections to the supplied SQL. It instead resolves an affected association by an immediate separate fetch, even if the mapping requests lazy fetching. This does not add filtering predicates to the application’s native SQL itself.

Second-level entity cache reads are bypassed for loads affected by enabled load-by-key filters and for entities with SQL restrictions on themselves or on reachable to-one associations, including associations inside embeddables. Entities whose static SQL restrictions prevent cache reads are also excluded from cache puts. This applies to inserts, loads, and updates, and retains cache invalidation on mutation. Enabled filters declared directly on a to-one association also prevent cache reads and puts for affected owners, regardless of applyToLoadByKey. An otherwise unrestricted target remains eligible for caching. Entity state containing filtered null associations is not stored in the second-level cache, and updates invalidate such entries instead of publishing the incomplete state. The built-in tenant filter on the loaded entity does not by itself cause this bypass, since entity cache keys already include the tenant identifier. Tenant restrictions on association targets still require SQL to establish their visibility. Other enabled load-by-key filters which affect the load still cause a bypass when tenant filtering is also active. Full query-cache entries retain the association keys needed for this distinction. Shallow query-cache hits obtain the distinction when reloading the entity. Consequently, applications may observe additional SQL and fewer second-level entity cache hits.

These changes address the inconsistencies described in HHH-19568 and its related issues.

Custom SQL updates with generated values

An @SQLUpdate statement retains its fixed parameter layout when an in-memory generated value, such as @UpdateTimestamp, requires dynamic update processing internally. Hibernate now binds every updateable attribute required by the custom statement, including unchanged attributes. This corrects missing or misplaced parameter bindings in the graph mutation queue and matches the legacy mutation queue. It applies independently of association filtering. Combining @DynamicUpdate and @SQLUpdate on the same entity remains unsupported.

SQL restrictions on collection elements mapped to multiple tables

When rendering a collection-element @SQLRestriction, Hibernate now qualifies an unambiguous mapped column using the table which declares it. This fixes restrictions on collections of JOINED subclasses which refer to a column stored in a superclass table, as described in HHH-12016. The same qualification applies to mapped columns stored in a @SecondaryTable, for both one-to-many and many-to-many collections. The qualification handles quoted column names and case differences in unquoted names. Unmapped or ambiguous column names retain the existing default qualification. This does not introduce an explicit alias declaration facility for @SQLRestriction.

Collection remove/create callback order

When moving a collection between owners or roles requires both removal of its old state and creation of its new state, Hibernate now invokes the collection-remove interceptor hook before the legacy collection-recreate hook. The corresponding Hibernate collection events and semantic mutation completion are likewise remove-first. Integrations must not retain the previous assumption that the recreate interceptor hook runs before the remove hook.

Collection mutation lifecycle timing

Collection post-events, cache handling, successful statistics, and queued-operation finalization are now tied to semantic collection-mutation completion instead of to the last row operation produced during decomposition. This ensures that grouped, reordered, and batched row work completes the correct collection lifecycle and that failed physical work suppresses successful completion handling.

A collection mutation which produces no SQL now completes directly after its semantic work is sealed; Hibernate no longer creates a SQL-shaped no-op solely to carry its post-event. Consequently, applications must not rely on a global order between pre/post collection events for unrelated collections. Each mutation’s pre-event still precedes its post-event, and a post-event still follows all physical work belonging to that mutation.

Collection owner update callback timing

Jakarta Persistence owner update callbacks are coordinated once per owner and flush. When entity and non-inverse collection work both contribute to an applicable owner update, one @PreUpdate callback is invoked and one @PostUpdate callback is delayed until all contributing entity and collection post-events have been delivered. Collection create or remove work associated with owner insert/delete lifecycle does not by itself establish update-callback applicability.

JDBC resource release in autocommit mode

When no transaction is active (autocommit), Hibernate now correctly releases the connection after statement execution if the configured ConnectionReleaseMode is set to AFTER_TRANSACTION. Previously, with DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION connection handling mode, JDBC resources and the physical connection were held until an explicit call to afterTransaction() or closing the session, even though in autocommit mode each statement is effectively its own transaction.

This change ensures that JDBC connections are promptly returned to the pool after each statement when there is no active transaction, preventing potential resource leaks.

As a consequence, accessing LOB locators (i.e. java.sql.Blob or java.sql.Clob typed attributes) outside of an active transaction will no longer work and result in an exception on most dialects. Previously this appeared to work only because the connection happened to remain open; however, there were no transactional isolation guarantees — the data read from the LOB could have already been modified by another transaction.

Note that this only affects entity attributes mapped as java.sql.Blob or java.sql.Clob, since these types rely on a JDBC locator that requires a live connection. Attributes mapped as byte[] or String are always eagerly materialized when the result set is read, and are not affected by this change. Similarly, some dialects that always use materialized LOB access are also unaffected.

Applications that use Blob or Clob typed attributes must access them within an active transaction:

// Before (assuming no active transaction, may have accidentally worked, but with no isolation guarantees):
session.get(MyEntity.class, id);
entity.getData().getBinaryStream(); // may now fail — connection already released

// After (correct):
session.getTransaction().begin();
session.get(MyEntity.class, id);
entity.getData().getBinaryStream(); // works — connection held by the transaction
session.getTransaction().commit();

Null elements of inverse indexed lists

A null element of an inverse @OneToMany list mapped with @OrderColumn again leaves a gap in the order column, so that the element is read back as null and the elements after it keep their list positions.

Since 6.2 the order column was instead written compacted, and a list persisted as [a, null, b] was stored with the index values 0,1 rather than 0,2 and loaded back as [a, b], silently shifting the elements after the null. Applications that came to rely on that compaction — for example by treating the reloaded list size as the number of non-null elements — will now see the null elements preserved across a round-trip, as they were through 5.x and 6.1.

Element collections are unaffected: a null element of an @ElementCollection list has never been persisted, and such lists still load back without it.

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.

Discriminator-Based Multi-Tenancy Changes

Hibernate now restricts generated mutation SQL by tenant and checks tenant ownership before changing secondary tables or collections. Stateless updates and deletes reject detached entities with a missing or mismatched tenant identifier before executing SQL. The root tenant is exempt from these restrictions.

Root tenant read operations now bypass the second-level cache. Mutations performed by the root tenant invalidate cache entries belonging to the actual owning tenant.

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.

Stateless updates and attributes excluded from versioning

StatelessSession.update() and EntityAgent.update() now leave the version invariant when only attributes annotated @ExcludedFromVersioning have been modified.

Unknown query parameter types

As required by JPA 4.0, Parameter.getParameterType() now throws IllegalStateException instead of returning null when the Java type is unknown. This can occur for native SQL parameters before binding, or HQL parameters whose type cannot be inferred. Binding a value with an explicit Java type establishes the parameter type, even when the value is null.

Query result type mismatches

With JPA bootstrap, a query result type mismatch now produces IllegalArgumentException instead of QueryTypeMismatchException.

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.

Multi-ID loading session check default

The default value of FindMultipleOption.SessionCheckMode has been changed from DISABLED to ENABLED.

This means that Session#findMultiple() and Session#getMultiple() now check the persistence context by default before querying the database. Identifiers for entities already managed in the current session will not be sent to the database, and entities marked for removal will be replaced with null in the result (see RemovalsMode).

To restore the previous behavior, pass FindMultipleOption.SessionCheckMode.DISABLED as a find option:

session.findMultiple(MyEntity.class, ids, SessionCheckMode.DISABLED);

Or, for the legacy byMultipleIds() API:

session.byMultipleIds(MyEntity.class).enableSessionCheck(false).multiLoad(ids);

@CurrentTimestamp(source = SourceType.DB) handling for MySQL-like databases

The MySQLDialect was using the now() function to implement fetching of the current timestamp, which returns no fractional seconds. Since the implementations for other databases all at least returned values with millisecond precision, the implementation was changed to include the maximum precision 6, by using now(6).

The effect of the change is that newly updated/inserted entities that use @CurrentTimestamp will now assign a value with the configured precision of the column, instead of always truncating the fractional seconds.

To revert back to the old behavior, change the column type of the annotated attribute to datetime(0),

alter table TBL
change column COL COL datetime(0);

Transaction requirements for locking APIs

EntityManager.getLockMode() now returns NONE for a managed entity outside a transaction instead of throwing TransactionRequiredException. With JPA bootstrap, lock(entity, LockModeType.NONE) now requires an active transaction. Stateless refresh() with a lock mode other than NONE also requires a transaction with JPA bootstrap.

Version checks for locking queries

A query requesting a pessimistic lock now checks the version of an already managed versioned entity even when its current lock mode is NONE. If another transaction has changed its version, the query throws OptimisticLockException instead of acquiring the lock on the stale instance. This includes entities loaded outside a transaction or retained in the persistence context after an earlier transaction. This behavior is consistent with EntityManager.lock(). Queries which request no lock are unaffected.

Locking queries now also perform the optimistic checks and forced version increments requested for entities already managed by the session. This applies to streamed results and follow-on locking as well as ordinary query results.

Exceptions for pessimistic lock failures

Hibernate now more consistently chooses between PessimisticLockException and LockTimeoutException based on whether the database locking failure aborts the transaction or only the statement.

Version check for LockModeType.OPTIMISTIC

For a managed entity locked with mode OPTIMISTIC or READ, Hibernate verifies the version before a transaction commits. Previously, on databases with snapshot isolation, this verification could read a stale version and miss a concurrent update.

Hibernate now acquires a pessimistic lock when necessary. Therefore, optimistic version checks may wait for other transactions or encounter lock timeouts or deadlocks.

Default allocation size for @NativeGenerator

When @NativeGenerator was introduced in Hibernate ORM 7.0, it defaulted to an allocation size of 1 for, in case the @SequenceGenerator or @TableGenerator annotations had their allocation size set to their default of 50. Other values were respected, so this behavior was slightly unintuitive, yet the reasoning behind the choice was not documented. To make the behavior less surprising, it was now changed to respect the allocation size of 50.

This requires that, if sequences are used, the sequence is altered to change the allocation size from 1 to 50. Alternatively, users can configure the allocation size to match the legacy value 1 by defining @NativeGenerator(sequenceForm = @SequenceGenerator(allocationSize = 1), tableForm = @TableGenerator(allocationSize = 1).

Unknown column values for @EnumeratedValue enums

When an enum is mapped via @EnumeratedValue, a column value which matches none of the declared enum values now results in an IllegalArgumentException while the row is read, consistent with the EnumType.STRING mapping (which relies on Enum.valueOf()). Previously such a value was silently read as null, which typically surfaced much later as a NullPointerException or as a wrong attribute value. Applications which relied on the lenient behavior, e.g. while an enum value is being removed, must clean up the affected rows before the enum value is dropped.

Conflicting interceptor settings are now rejected

Configuring both hibernate.session_factory.interceptor and hibernate.session_factory.session_scoped_interceptor now throws ConflictingInterceptorSettingsException when building the SessionFactory. Previously, one setting silently took precedence over the other with no indication that both had been configured.

Applications that set both must choose one: hibernate.session_factory.interceptor for a shared instance or CDI-managed global interceptor, or hibernate.session_factory.session_scoped_interceptor for a per-session interceptor obtained from a Supplier or a CDI-managed class.

Changes in XSD

This section describes changes in XML Schema Descriptors

Use of HTTPS for mapping.xsd namespace

The namespace for mapping.xsd is now https://www.hibernate.org/xsd/orm/mapping, using HTTPS instead of HTTP. Hibernate will handle this on the fly at runtime, though you should also consider changing your mapping documents using this XSD either manually or using the hibernate-migration-recipes OpenRewrite recipes.

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).

Interceptor.onLoad() callback implemented for Stateless Sessions

In previous versions the Interceptor.onLoad() callback had not been implemented for Stateless Sessions. This has now been corrected.

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.

Unique constraints for sets with nullable elements

Generated unique constraints for Set collection tables with nullable element columns now use NULLS NOT DISTINCT where supported, including PostgreSQL 15 and later. This prevents duplicate elements whose corresponding columns contain nulls. Before applying the new constraint to an existing table, remove any such duplicates.

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 published artifacts

Removal of OSGi metadata

Hibernate ORM JARs no longer include OSGi metadata in their manifests, including Bundle-*, Import-Package, Export-Package, and Require-Capability headers. Applications deploying Hibernate ORM in an OSGi environment must supply the required bundle metadata themselves. The Automatic-Module-Name manifest attribute is retained for use on the Java module path.

Relocation POMs from org.hibernate are no longer published

Previous releases published relocation POMs under the org.hibernate group id, directing consumers to the current org.hibernate.orm coordinates. Starting with this version — these relocation POMs are no longer published.

If your build still references Hibernate ORM modules using the old org.hibernate group id (e.g. org.hibernate:hibernate-core), you must update your dependency coordinates to use org.hibernate.orm (e.g. org.hibernate.orm:hibernate-core).

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.

Bean container lifecycle

ContainedBeanImplementor has been removed; its initialize() and release() methods are now declared by ContainedBean. Custom handles must implement these lifecycle hooks. Already initialized handles may use a no-op initialize(), and handles with no cleanup requirements may use a no-op release(). Release must not initialize an unresolved bean. ManagedBean remains the consumer-facing access contract. Consumers should release through the registry or owning BeanContainer, so cache eviction and lifecycle bookkeeping occur.

BeanLifecycleStrategy and AbstractCdiBeanContainer have moved from org.hibernate.resource.beans.container.spi to org.hibernate.resource.beans.container.internal. They are Hibernate implementation details; external containers express the requested lifecycle policy through BeanContainer.LifecycleOptions.

FallbackContainedBean has likewise moved from beans.container.spi to beans.container.internal. ProvidedInstanceManagedBeanImpl and ManagedBeanRegistryInitiator have moved from beans.spi to beans.internal (all under org.hibernate.resource).

ManagedBeanRegistry#getBeanContainer() is now annotated @Nonnull and always returns a container. When no backing container is configured, Hibernate uses FallbackBeanContainerImpl. Use BeanContainer#isFallback() to distinguish this case. The fallback container uses the supplied BeanInstanceProducer, including custom producers.

BeanContainer implementations must implement getBootstrapSafeBean() and releaseBean(). Bootstrap-safe acquisition must return a handle without requiring the backing container to be ready. Releasing a handle must remove its cache and lifecycle registrations and destroy its instance, if any, without initializing an unresolved bean. Releasing an already-released or foreign handle has no effect. Callers must finish using a shared bean before explicitly releasing it.

AbstractBeanContainer supplies concurrent reusable-reference caching and deferred acquisition. It does not track distinct beans for destruction; implementations needing destruction must track and release those beans, as AbstractCdiBeanContainer does. The registry no longer maintains a second cache or destruction list. Custom containers are responsible for honoring LifecycleOptions; disabling cached references must bypass their caches, while JPA-compliant creation controls independent instance creation. Container shutdown must occur after acquisition, instance access, and explicit release have completed.

Shared identifier columns in formula one-to-one associations

For an owning @OneToOne association containing a join formula, Hibernate now treats physical join columns that belong to the owner’s primary key as read-only for that association. The same rule applies to Hibernate mapping XML 8.0 and newer. It applies even when a shared column explicitly declares insertable=true or updatable=true, and independently of target primary-key or unique-key binding.

Some declarations previously rejected as duplicate writable-column mappings now work without explicit insertable=false, updatable=false. Conversely, partially shared tuples may now fail mapping validation when the inferred read-only identifier columns conflict with the remaining physical columns' write flags. Formula placement does not hide these conflicts; Hibernate does not silently clear the other columns' write flags.

Mappings that relied on the association to supply an owner identifier column’s INSERT value lose that behavior, including when the identifier mapping itself is non-insertable. Supply the identifier through its identifier mapping or generator, or use an appropriate existing identity-defining mapping. Association assignment does not infer @MapsId semantics or copy an identifier from the associated entity.

Writable non-identifier mixed joins, formula-free associations, declared many-to-ones, identity-defining associations, join-table mappings, legacy hbm.xml, and Hibernate XML 7.0 retain their existing semantics. Explicit @NotFound policies and unique-key references keep their existing association binding paths while receiving the per-column read-only inference where applicable.


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.