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()-EntityAgentdefines avoidreturn type, whereas Hibernate has historically returned the identifier of the inserted entity. -
fetch()-EntityAgentdefines that the fetched value be returned, whereasStatelessSessionhas historically defined avoidreturn 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.
Another impact is that @NamedQuery and @NamedNativeQuery (as well as Hibernate’s variants) may no longer be used to define mutation queries -
-
@NamedQuery- ASelectionQuerydefined using HQL/JPQL -
@NamedStatement- AMutationQuerydefined using HQL/JPQL -
@NamedNativeQuery- ASelectionQuerydefined using native SQL -
@NamedNativeStatement- AMutationQuerydefined 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
ProcedureOutputsto avoid casts and, even worse, often untyped casts. This is achieved by the new methods added toOutput. 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.
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.ActionQueuewas renamed toActionQueueLegacyimplementing the neworg.hibernate.action.queue.spi.ActionQueueinterface -
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.MutationTargetintoGraphMutationTargetandLegacyMutationTarget -
StateManagementsupport for graph-aware collection/entity state changes -
Batchsplit-
Batchnarrowed to lifecycle only -
GroupedBatch(extendsBatch) supports legacy "grouped statement" execution -
SingleStatementBatch,StatementBinder, andBatchedResultCheckersupport 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
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 underorg.hibernate.orm.core. -
HHH10001005: Database info: …, previously logged underorg.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()andupsertMultiple()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 nothingwith explicit conflict columns now usesMERGEemulation 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
finalmodifier from persistent fields, or -
remove the
finalmodifier from the persistent field declarations, or -
for
@IdClasstypes, annotate a constructor with@Instantiatorto use constructor-based instantiation (HHH-20542), or -
for
@IdClasstypes, switch to a Javarecord.
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 |
|
The reference remains absent. An unrelated update does not create a join-table row. |
Visible target |
|
The reference is cleared according to the mapping. |
Filtered target |
|
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.DDLis deprecated for removal. -
A new setting
hibernate.tooling.schema.apply_validation_constraintshas been added toSchemaToolingSettingsas the way to control the influence of Jakarta Validation constraints on the generated DDL schema. Valid values are defined byValidationConstraintDdlInfluence:-
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_ddlis 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
Eis 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 typeE, 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).
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
HQL in |
|
|
Custom |
HQL with |
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).
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.