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

Java 11

With 6.0, Hibernate ORM has moved to expect Java 11 as its baseline version.

Jakarta Persistence

6.0 moves from Java Persistence as defined by the Java EE specs to Jakarta Persistence as defined by the Jakarta EE spec. The most immediate impact of this change is that applications would need to be updated to use the Jakarata Persistence classes (jakarta.persistence.*) instead of the Java Persistence ones (javax.persistence.*).

The Jakarta spec also renames the JPA settings (again, from javax.persistence.* to jakarta.persistence.*) and defines a new set of XSD namespaces for orm.xml and persistence.xml files.

Jakarta provides a transformer tool which, along with appropriate "rules", will transform a project from Java Persistence to Jakarta Persistence. This can update package names in source, settings, xsd references and more.

As far as the XSD and setting changes, Hibernate does support both sets as a temporary aid in migration. It logs a deprecation warning when the Java EE variants are used. See the rules/ directory in the project root for the configuration used to migrate Hibernate itself.

Reading from JDBC

One of the main reasons for 6.0 development was the move from reading results from the JDBC ResultSet by name (read-by-name) as done in previous versions of Hibernate, to reading the results by position (read-by-position).

Throughput testing of Hibernate showed that its use of read-by-name was its limiting factor in any further scaling in terms of throughput - much of the issue was actually the call into the ResultSet. We like to improve performance all the time :)

This change, along with Generated SQL, helped achieve this goal.

As discussed in Type system though, this change has a very big impact on Hibernate’s mapping type system

Generated SQL

  1. Column aliases are no longer generated.

  2. Column references are "unique-d".

  3. Better definition of joins

  4. Better determination of unnecessary joins (secondary tables, inheritance tables)

Identifier as Object

Previous versions of Hibernate required that all identifier types implement Serializable. 6.0 removes this restriction - identifiers can be any Object.

This change affects many api and spi methods previously defined using Serializable.

@IdGeneratorType

6.0 adds a new @IdGeneratorType annotation that allows better, type-safe way to define custom generators to use for identifier generation.

Implicit Identifier Sequence and Table Name

The way in which Hibernate determines implicit names for sequences and tables associated with identifier generation has changed in 6.0 which may affect migrating applications.

As of 6.0, Hibernate by default creates a sequence per entity hierarchy instead of a single sequence hibernate_sequence.

Due to this change, users that previously used @GeneratedValue(strategy = GenerationStrategy.AUTO) or simply @GeneratedValue (since AUTO is the default), need to ensure that the database now contains sequences for every entity, named <entity name>_seq. For an entity Person, a sequence person_seq is expected to exist. It’s best to run hbm2ddl (e.g. by temporarily setting hbm2ddl.auto=create) to obtain a list of DDL statements for the sequences.

Users that use a import.sql file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a import.sql file contains references to the hibernate_sequence, these have to be replaced with <entity name>_seq. Also note that the default allocation size changed for such implicit sequences.

To help with backwards compatibility, or to apply any general naming strategy, 6.0 introduces the org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy contract which can be specified using the hibernate.id.db_structure_naming_strategy setting. See discussion at link:https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/cfg/AvailableSettings.html#ID_DB_STRUCTURE_NAMING_STRATEGY

For backwards compatibility, use either hibernate.id.db_structure_naming_strategy=single or hibernate.id.db_structure_naming_strategy=legacy depending on needs

Defaults for implicit sequence generators

Implicit sequences, like the hibernate_sequence before, now respect the defaults of the JPA @SequenceGenerator annotation, which means that the sequences have an allocation size of 50.

Users that use a import.sql file to import test or static data during application boot or for tests have to make sure the new sequences match expectations of Hibernate. If a import.sql file contains inserts to a table, the sequence for that table has to be reset to <max id value of table> + 1 + <allocation size>. This is because Hibernate interprets the value returned by the sequence as hi-value for its pooled optimizer. An alternative is to reset the sequence to <max id value of table> + 1 and also configure the sequence generator to that initial value, by specifying @SequenceGenerator(sequenceName = "<entity name>_seq", initialValue = <max id value of table> + 1) on every entity.

Users are recommended to do the former and add statements like alter sequence <entity name>_seq restart with <max id value of table> + 1 + <allocation size> e.g. alter sequence person_seq restart with 53 if id values 1 and 2 are used.

To help with backwards compatibility, or to apply any general naming strategy, 6.0 introduces the org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy contract which can be specified using the hibernate.id.db_structure_naming_strategy setting. See discussion at link:https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/cfg/AvailableSettings.html#ID_DB_STRUCTURE_NAMING_STRATEGY

For backwards compatibility, use hibernate.id.db_structure_naming_strategy=legacy.

Type system

Another change is to generally modernize Hibernate’s mapping annotations and make them more type-safe.

We decided this is the right time since 6.0 is a major release and most of the type-related contracts were already changing to implement the read-by-position changes.

One part of this work was the removal of various String-based approaches for specifying Types to use from annotations, including the removal of @AnyMetaDef, @AnyMetaDefs, @TypeDef and @TypeDefs, as well as removing annotation attributes accepting the type to use as a String (e.g. org.hibernate.annotations.CollectionType#type)

The User Guide covers the details of mapping your domain model.

Renaming of JavaTypeDescriptor contract

The interface org.hibernate.type.descriptor.java.JavaTypeDescriptor has been renamed to org.hibernate.type.descriptor.java.JavaType

Renaming of SqlTypeDescriptor contract

The interface org.hibernate.type.descriptor.sql.SqlTypeDescriptor has been renamed to org.hibernate.type.descriptor.jdbc.JdbcType.

Basic types

Basic mappings are no longer configurable through the BasicType contract. Instead, users configure the different aspects of mapping the basic value to the database -

  • JavaType

  • JdbcType

  • BasicValueConverter [1]

  • MutabilityPlan

This also made the various implementations of BasicType obsolete, thus they have been removed. NamedBasicTypeImpl takes the role of all the previous specific implementations by wrapping a JdbcType and JavaType.

The StandardBasicTypes class previously exposed BasicType instance fields, which now have been replaced with fields of the type BasicTypeReference. APIs that previously accepted just a BasicType have been adapted to also accept a BasicTypeReference which allows for uses of StandardBasicType fields to stay mostly source compatible.

UserType

UserType is still supported, and is specified using the new Type annotation.

Boolean converters

Hibernate now provides standard AttributeConverter implementations for handling different database representations as boolean values in the domain model:

YesNoConverter

Handles values stored in the database as either Y or N. Replaces the removed YesNoBooleanType (yes_no)

TrueFalseConverter

Handles values stored in the database as either T or F. Replaces the removed TrueFalseBooleanType (true_false)

NumericBooleanConverter

Handles values stored in the database as either 1 or 0. Replaces the removed NumericBooleanType (numeric_boolean)

E.g.

@Type(type="yes_no")
boolean isActive;

becomes

@Convert(converter=YesNoConverter.class)
boolean isActive;

In fact, if your application consistently maps booleans to the same database representation you can even register one as an auto-apply converter.

Embeddables / components

Mapping of embeddables had a few changes as well.

Different embeddable mappings

Multiple component mappings for the same Java class with different property mappings is no longer supported. Every property mapping combination should have its own Java class

EmbeddableInstantiator

6.0 introduces the new EmbeddableInstantiator contract.

EmbeddableInstantiator supports constructor-injection! Note, however, that embeddables used as identifiers cannot use constructor injection.

CompositeUserType changes

The CompositeUserType interface was re-implemented to be able to model user types as proper embeddable types. A major difference to 5.x is the introduction of an "embeddable projection" that is used to determine the mapping structure.

Previously, a CompositeUserType had to provide property names and types through dedicated accessor methods, but this was complicated for non-basic mappings and required quite some knowledge about Hibernate internals. With 6.0 these methods are replaced with a method that returns an "embeddable projection" class. The class is like a regular @Embeddable class and is used to determine the mapping structure for the CompositeUserType.

Component values of a user type object are accessed by property index. The property index is 0-based and can be determined by sorting the persistent attribute names lexicographically ascending and using the 0-based position as property index.

For example, the following component:

public class MonetaryAmountEmbeddable {
	BigDecimal value;
	Currency currency;
}

will assign property index 0 to currency and index 1 to value.

Note that it is not possible anymore to use @Columns to specify the names of columns of a composite user type mapping. Since a CompositeUserType now constructs a proper component, it is necessary to use the @AttributeOverride annotation.

Plural attributes

6.0 defines 2 main ways to influence collection mapping @CollectionType and @CollectionTypeRegistration

@CollectionType

The @CollectionType annotation is kept from 5.x. However, where it used to define

String type();

it now defines

Class<? extends UserCollectionType> type();

The type to use must be a UserCollectionType (can no longer be a CollectionType) and it no longer works with type-definitions. See Type system for further discussion of general type changes.

@CollectionTypeRegistration

Allows to "auto apply" a UserCollectionType whenever Hibernate encounters a particular plural attribute classification

SQL BIGINT/count() mapping changes

The type code SqlType.BIGINT is now mapped to the Java type Long by default; it previously was (incorrectly) mapped to BigInteger.

As a result, native queries whose "select" clause returns a BIGINT element will now convert that element to Long, instead of BigInteger previously. This will be the case for native queries returning a count() in particular.

Duration mapping changes

Duration now maps to the type code SqlType.INTERVAL_SECOND by default, which maps to the SQL type interval second if possible, and falls back to numeric(21). In either case, schema validation errors could occur as 5.x used the type code Types.BIGINT.

Migration to numeric(21) should be easy. The migration to interval second might require a migration expression like cast(cast(old as numeric(21,9) / 1000000000) as interval second(9)).

To retain backwards compatibility, configure the setting hibernate.type.preferred_duration_jdbc_type to BIGINT.

UUID mapping changes

UUID now maps to the type code SqlType.UUID by default, which maps to the SQL type uuid if possible, and falls back to binary(16). Due to the change to the native uuid type, schema validation errors could occur on database with native data type support.

The migration to uuid might require a migration expression like cast(old as uuid).

To retain backwards compatibility, configure the setting hibernate.type.preferred_uuid_jdbc_type to BINARY.

Instant mapping changes

Instant now maps to the type code SqlType.TIMESTAMP_UTC by default, which maps to the SQL type timestamp with time zone if possible, and falls back to timestamp. Due to this change, schema validation errors could occur on some databases.

The migration to timestamp with time zone might require a migration expression like cast(old as timestamp with time zone).

To retain backwards compatibility, configure the setting hibernate.type.preferred_instant_jdbc_type to TIMESTAMP.

Query

Quite a few changes have been made to how Query works.

SQM (HQL/Criteria)

Another major change in 6.0 is the move to a dedicated tree structure to model HQL and Criteria queries. This tree structure is called the Semantic Query Model, or SQM for short.

Result "rows"

Queries that use joins without specifying a select clause (e.g. from Person p join p.address) used to return a List<Object[]>. Starting with 6.0, such a query instead returns List<Person>

The HQL query select p, a from Person p join p.address a returns instead a List<Object[]>.

List<Person> result = session.createQuery("from Person p join p.address").list();
List<Object[]> results

Multi-table Mutation Queries

The implementations for bulk SQM DML statements like insert, update and delete were significantly improved in 6.0. An important bug fix is, that delete statements now properly clean up collection tables. insert statements now also support inserting into multi-table entities by making use of special purpose temporary tables into which the insert goes and is then split up into the respective tables.

There are currently 2 implementation strategies:

  • Using temporary tables (the default)

  • Using DML in CTEs (used on DB2 and PostgreSQL)

The temporary table approach is pretty simple and works in a similar way to how 5.x already implemented it. Data or primary key values are first inserted into a temporary table and then the DML changes are applied to the various tables that are affected by the SQM DML statement.

The CTE approach is new and implements a more performant approach by executing a single statement, containing the various individual DML statements that would normally be executed separately. This allows to run SQM DML statements in a single JDBC operation that does not move any data between the database and the application, which should provide a significant boost for statements that involve many rows.

Note that the configuration property hibernate.hql.bulk_id_strategy was changed to hibernate.query.mutation_strategy which will now refer to classes or objects implementing org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy.

Hibernate Criteria behavior change

By default, when bootstrapping Hibernate through the native bootstrap APIs or when explicitly disabling the newly introduced hibernate.criteria.copy_tree configuration property, it is expected that criteria queries passed to jakarta.persistence.EntityManager#createQuery(CriteriaQuery), jakarta.persistence.EntityManager#createQuery(CriteriaUpdate) or jakarta.persistence.EntityManager#createQuery(CriteriaDelete) are not mutated afterwards to avoid the need for copying the criteria query.

Prior to 6.0, mutations to criteria queries didn’t affect Query instances created from that. To retain backwards compatibility, enable the hibernate.criteria.copy_tree configuration property.

Pass-through tokens

The use of plain HQL identifiers in e.g. functions which couldn’t be interpreted as an attribute of a FROM root were passed through as-is to SQL in Hibernate 5.x which was dropped in 6.0 because we believe this is unsafe and might lead to surprising results. HQL queries that relied on this, need to be changed and use the newly introduced sql function, which allows passing through the content of a string literal to SQL.

An HQL query like select substring( e.description, 21, 11, octets ) from AnEntity e, which relies on this for passing through octets can be migrated to select substring( e.description, 21, 11, sql('octets') ) from AnEntity e.

FROM token now disallowed for UPDATE

In Hibernate 5, the optional keyword FROM was allowed for update statements like update from MyEntity e set e.attr = null. Support for this was removed to more align with JPQL. Just remove the from keyword from your existing update statements.

DISTINCT

Starting with Hibernate ORM 6 it is no longer necessary to use distinct in JPQL and HQL to filter out the same parent entity references when join fetching a child collection. The returning duplicates of entities are now always filtered by Hibernate.

Which means that for instance it is no longer necessary to set QueryHints#HINT_PASS_DISTINCT_THROUGH to false in order to skip the entity duplicates without producing a distinct in the SQL query.

From Hibernate ORM 6, distinct is always passed to the SQL query and the flag QueryHints#HINT_PASS_DISTINCT_THROUGH has been removed.

Association Comparisons

Previously Hibernate did allow comparing an association with an FK value like …​ where alias.association = 1 or …​ where alias.association = alias.association.id or even …​ where alias.association = :param where param is bound to an integer 1. This was supported prior to Hibernate 6.0 if the foreign key for the association is an integer.

The right way to do this is de-referencing the association by the FK attribute …​ where alias.association.id = 1 which is guaranteed to not produce a join, or use an entity reference for …​ where alias.association = :param where param is bound to entityManager.getReference(EntityClass.class, 1).

Query Path comparison

Prior to 6.0 it was possible to compare two paths of different types or comparing an entity path against a literal e.g.:

session.createQuery( "select a from MyEntity a, MyEntity b where a = b.id" )

or

session.createQuery( "select a from MyEntity a where a = 123")

This is not allowed anymore. The queries have to be changed to

session.createQuery( "select a from MyEntity a, MyEntity b where a = b" )

and

session.createQuery( "select a from MyEntity a where a.id = 123");

The same is true for the left and right hand side types of a comparison predicate. Queries like

Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );

// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.get( "id" ).in( subQuery ) );

will be considered invalid and should be changed into

Root<Participation> root = criteria.from( Participation.class );
Root<Submission> rootSubQuery = subQuery.from( Submission.class );
subQuery.select( rootSubQuery.join( "submitters" ) );

// left hand side of type Submission.id and right hand side of Submissions types
criteria.where( root.in( subQuery ) );

Collection pseudo-attributes

Prior to 6.0, it was possible to de-reference special properties on plural attributes like size which was dropped. The special properties lead to confusion and were sometimes ambiguous. The replacement is the function syntax.

size

The collection size can be determined by using the size( pluralAttribute ) function instead

elements

The collection elements can be referred to by using the value( pluralAttribute ) function instead

indices

The collection indices can be referred to by using the index( pluralAttribute ) or key( pluralAttribute ) function instead

index

The collection index can be referred to by using the index( pluralAttribute ) or key( pluralAttribute ) function instead

maxindex

The collection maximum index can be determined by using the maxindex( pluralAttribute ) function instead

minindex

The collection minimum index can be determined by using the minindex( pluralAttribute ) function instead

maxelement

The collection maximum element can be determined by using the maxelement( pluralAttribute ) function instead

minelement

The collection minimum element can be determined by using the minelement( pluralAttribute ) function instead

NativeQuery

As NativeQuery extends from Query, all the changes listed in Query also apply to NativeQuery.

Some additional changes apply specifically to NativeQuery

Ordinal Parameters binding

HQL ordinal parameter binding is 1-based, this means that queries like

s.createQuery( "select p from Parent p where id in ?0", Parent.class );
query.setParameter( 0, Arrays.asList( 0, 1, 2, 3 ) );

that uses a 0-based positional binding are not supported, and they should be changed to the following

s.createQuery( "select p from Parent p where id in ?`", Parent.class );
query.setParameter( 1, Arrays.asList( 0, 1, 2, 3 ) );

ProcedureCall / StoredProcedureQuery Parameters

For parameters defined on a ProcedureCall as accepting binding (IN and INOUT), a distinction is now made between whether setParameter is called or not. If setParameter was called, whatever value was set by the user is passed to the database. If it was not called, Hibernate will not set any value which triggers the default value defined on the database procedure argument be used

Query result cache

Another change in 6.0 is related to the query result cache.

In previous versions, when the query-cache is enabled and a query returning entities is executed, only the entity identifiers were stored in the query-cache. If second-level caching is enabled for a returned entity, the entity data was stored in its second-level cache region.

Storing just the identifiers in the query-cache has a major drawback when fetching is defined for the query (dynamic fetch, entity-graph, etc) as it can, and often does, lead to N+1 selects.

Starting in 6.0, we now store the complete set of data for the entity into the query-cache. This also can have a drawback related to the size of caches. We plan to address this further in later 6.x releases to allow storing just the identifiers along the lines of the previous behavior.

E.g.

Statistics stats = sessionFacroty.getStatistics();

// First time the query is executed, query and results are cached and both the query and entity chache will be populated.

TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
							.setHint( HINT_CACHEABLE, true );

List<Employee> employees = query.getResultList();
assertEquals( 1, employees.size() );

assertEquals( 0, stats.getQueryCacheHitCount() );
assertEquals( 1, stats.getQueryCacheMissCount() );
assertEquals( 1, stats.getQueryCachePutCount() ); // query cache is populated.

assertEquals( 0, stats.getSecondLevelCacheHitCount() );
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
assertEquals( 1, stats.getSecondLevelCachePutCount() ); // entity cache is populated as well.

stats.clear();

// Second time the same query is executed only the query cache will be hit.

TypedQuery<Employee> query = session.createQuery( "select e from Employee e", Employee.class )
							.setHint( HINT_CACHEABLE, true );
List<Employee> employees = query.getResultList();
assertEquals( 1, employees.size() );

assertEquals( 1, stats.getQueryCacheHitCount() ); // the query cache is hit.
assertEquals( 0, stats.getQueryCacheMissCount() );
assertEquals( 0, stats.getQueryCachePutCount() );

assertEquals( 0, stats.getSecondLevelCacheHitCount() );
assertEquals( 0, stats.getSecondLevelCacheMissCount() );
assertEquals( 0, stats.getSecondLevelCachePutCount() ); // No need to hit the entity cache because the query cache contains all the entity data.

Stream

jakarta.persistence.Query#getResultStream() and org.hibernate.query.Query#stream() no longer return a Stream decorator. In order to close the underlying IO resources, it is now necessary to explicitly call the Stream#close() method.

This change makes the Streams returned by Hibernate behave as defined in the JDK Stream documentation, which is quite explicit about the need for an explicit call to close by the user to avoid resource leakages.

Interceptor

The signature of the #onSave method has been changed from

boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

to

boolean onSave(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types)

to account for the general change in expected identifier type from Serializable to Object. See Identifier as Object.

If custom Interceptor implementations do not use @Override on their implementations, this can lead to situations where a custom Interceptor no longer overrides this method. Moral of the story…​ always use @Override - this is why it exists

Fetch circularity determination

As back-ground, Hibernate does understand whether a fetch is actually, truly circular. It simply understands that while walking a fetch-graph it encounters the same table/column(s) making up a particular foreign-key. In this case, it simply stops walking the graph any deeper.

Previous versions of Hibernate determined fetches using a depth-first approach, which occasionally led to odd "circularity" determination. Starting with 6.0, we now perform fetch determination using a width first approach.

Given a model such as

@Entity
class Node {

    @ManyToOne

    Node node1;

    @ManyToOne
    Node node2;

}

Hibernate previously generated joins by walking the entity association graph for the Node#node1 sub-tree prior to walking the Node#node2 sub-tree. Since the associations are all eager, Hibernate 6.0 now executes a query with 4 joins

FROM Node
JOIN Node.node1
JOIN Node.node1.node2
JOIN Node.node2
JOIN Node.node2.node1

whereas before it executed

FROM Node
JOIN Node.node1
JOIN Node.node1.node2

and issued a select for Node.node2 if the FK of Node.node2 was not null

FROM Node.node2
JOIN Node.node2.node1
JOIN Node.node2.node1.node2

In this simple example this is not such a big deal, but if the number of eager fetched self-associations is increased to e.g. 3 like here:

@Entity
class Node {

    @ManyToOne
    Node node1;

    @ManyToOne
    Node node2;

    @ManyToOne
    Node node3;

}

this results in mind-blowing 15 joins

FROM Node
JOIN Node.node1
JOIN Node.node1.node2
JOIN Node.node1.node2.node3
JOIN Node.node1.node3
JOIN Node.node1.node3.node2
JOIN Node.node2
JOIN Node.node2.node1
JOIN Node.node2.node1.node3
JOIN Node.node2.node3
JOIN Node.node2.node3.node1
JOIN Node.node3
JOIN Node.node3.node1
JOIN Node.node3.node1.node2
JOIN Node.node3.node2
JOIN Node.node3.node2.node1

as you can see, this leads to a lot of joins very quickly, but the behavior of 5.x simply was not intuitive. To avoid creating so many joins, and also in general, we recommend that you use lazy fetching i.e. @ManyToOne(fetch = FetchType.LAZY) or @OneToOne(fetch = FetchType.LAZY) for most associations, but this is especially important if you have multiple self-referencing associations as you can see in the example.

Restructuring of org.hibernate.loader

The contents of the loader.collection package were restructured into loader.ast.spi and loader.ast.internal as well as adapted to the SQM API.

The contents of loader.custom were adapted and moved to query.sql.

The contents of loader.entity and loader.plan were removed

Restructuring of the sql package

The contents of sql.ordering were adapted and moved to metamodel.mapping.ordering.ast.

Classes of the sql package that were previously used for building SQL, but aren’t needed anymore, were removed. The SQL generation is now fully handled through the SqlAstTranslator which a Dialect exposes a factory for.

Deprecation of hbm.xml mappings

Legacy hbm.xml mapping format is considered deprecated and will no longer supported beyond 6.x.

Association laziness now respected

Prior to Hibernate 6.0, lazy associations that used fetch="join" or @Fetch(FetchMode.JOIN) were considered eager when loaded by-id i.e. through Session#get/EntityManager#find, even though for queries the association was treated as lazy.

Starting with Hibernate 6.0, the laziness of such associations is properly respected, regardless of the fetch mechanism. Backwards compatibility can be achieved by specifying lazy="false" or @ManyToOne(fetch = EAGER)/@OneToOne(fetch = EAGER)/@OneToMany(fetch = EAGER)/@ManyToMany(fetch = EAGER).

hbm.xml <return-join/> behavior change

As of Hibernate 6.0, a <return-join/> will cause a fetch of an association, rather than adding a selection item. Consider the following example:

<sql-query name="organizationreturnproperty">
    <return alias="org" class="Organization">
        <return-property name="id" column="ORGID"/>
        <return-property name="name" column="NAME"/>
    </return>
    <return-join alias="emp" property="org.employments">
        <return-property name="key" column="EMPLOYER"/>
        <return-property name="element" column="EMPID"/>
        <return-property name="element.employee" column="EMPLOYEE"/>
    </return-join>
    ...
</sql-query>

Prior to 6.0, a query would return a list of tuples [Organization, Employee], but now this will return a list of Organization with an initialized employments collection.

Removals

The following features have been removed

hbm.xml multiple <column/> now disallowed

In 6.0 the support for basic property mappings with multiple columns was removed. The only use case for that was when a CompositeUserType was in use, which was reworked to now work on top of components.

Uses like:

<property name="salary" type="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
    <column name="CURRENCY"/>
    <column name="AMOUNT" sql-type="float"/>
</property>

have to be migrated to proper components:

<component name="salary" class="org.hibernate.orm.test.sql.hand.MonetaryAmountUserType">
    <property name="value" column="AMOUNT">
        <type name="float"/>
    </property>
    <property name="currency" column="CURRENCY"/>
</component>

The component class attribute now supports interpreting a CompositeUserType class properly.

Legacy Hibernate Criteria API

The legacy Hibernate Criteria API which was deprecated back in Hibernate 5.x and removed in 6.0. Usually, all queries using the legacy API can be modeled with the JPA Criteria API. In some cases it is necessary to use the Hibernate JPA Criteria extensions.

Iterate

The Query#iterate() method has been removed. The alternative is to use one of

  • Query#stream()

  • Query#getResultStream()

  • Get the Iterator from List returned by Query#list() / Query#getResultList()

Callable via NativeQuery

Using NativeQuery to call SQL functions and procedures is no longer supported. org.hibernate.procedure.ProcedureCall or jakarta.persistence.StoredProcedureQuery should be used instead.

@NamedNativeQuery references defining execution of procedure or functions should be migrated to use @NamedStoredProcedureQuery instead.

E.g., the following @NamedNativeQuery -

@NamedNativeQuery(
    name = "personAndPhones",
    query = "{ ? = call fn_person_and_phones( ? ) }",
    callable = true,
    resultSetMapping = "personWithPhonesResultMapping"
)

...

final List<Object[]> personAndPhones = entityManager
        .createNamedQuery("personAndPhones" )
        .setParameter( 1, 1L )
        .getResultList();

should be changed to use @NamedStoredProcedureQuery instead -

@NamedStoredProcedureQuery(
    name = "personAndPhones",
    procedureName = "fn_person_and_phones",
    resultSetMappings = "personWithPhonesResultMapping",
    hints = @QueryHint(name = "org.hibernate.callableFunction", value = "true"),
    parameters = @StoredProcedureParameter(type = Long.class)
)

Callable named native queries in hbm.xml files should be migrated to the orm.xml version.

E.g., the following <sql-query callable="true"> -

<sql-query name="simpleScalar" callable="true">
    <return-scalar column="name" type="string"/>
    <return-scalar column="`value`" type="long"/>
    { ? = call simpleScalar(:number) }
</sql-query>

...

final List<Object[]> results = entityManager
        .createNamedQuery("simpleScalar" )
        .setParameter( 1, 1L )
        .getResultList();

should be changed to use <named-stored-procedure-query/> instead -

<named-stored-procedure-query name="simpleScalar" procedure-name="simpleScalar">
    <parameter class="java.lang.Integer" mode="IN" name="number"/>
    <result-set-mapping>simpleScalar</result-set-mapping>
    <hint name="org.hibernate.callableFunction" value="true"/>
</named-stored-procedure-query>
<sql-result-set-mapping name="simpleScalar">
    <column-result name="name" class="java.lang.String"/>
    <column-result name="value" class="java.lang.Long"/>
</sql-result-set-mapping>
To ease the migration, <sql-query callable="true"/> and @NamedNativeQuery(callable = true) queries will be translated and registered as named stored procedure in 6.0, but future versions will drop this automatic translation.

Either org.hibernate.procedure.ProcedureCall or jakarta.persistence.StoredProcedureQuery can be used to execute the named query -

// Use StoredProcedureQuery
final List<Object[]> personAndPhones = entityManager
        .createNamedStoredProcedureQuery( "simpleScalar" )
        .setParameter( 1, 1L )
        .getResultList();

// Use ProcedureCall
final List<Object[]> personAndPhones = entityManager
        .unwrap( Session.class )
        .getNamedProcedureCall( "simpleScalar" )
        .setParameter( 1, 1L )
        .getResultList();

It is also no longer supported to execute procedures and functions via a dynamic (unnamed) NativeQuery. All such usages should be converted to use ProcedureCall or StoredProcedureQuery instead via Session#createStoredProcedureCall or EntityManager#createStoredProcedureQuery, respectively.

// Use StoredProcedureQuery
final List<Object[]> personAndPhones = entityManager
        .createStoredProcedureQuery( "fn_person_and_phones", "personWithPhonesResultMapping" )
        .setParameter( 1, 1L )
        .getResultList();

// Use ProcedureCall
final List<Object[]> personAndPhones = entityManager
        .unwrap( Session.class )
        .createStoredProcedureCall( "fn_person_and_phones", "personWithPhonesResultMapping" )
        .setParameter( 1, 1L )
        .getResultList();

HQL fetch all properties clause

The fetch all properties clause was removed from the HQL language without a replacement. A similar behavior can be achieved by constructing an entity graph and applying that as load graph:

EntityGraph<Document> entityGraph = entityManager.createEntityGraph( Document.class );
for ( Attribute<Document, ?> attr : entityManager.getMetamodel().entity( Document.class ).getAttributes() ) {
    entityGraph.addAttributeNodes( attr.getName() );
}
List<Document> documents = s.createQuery( "from Document", Document.class )
        .setHint( "jakarta.persistence.loadgraph", entityGraph )
        .getResultList();

JMX integration

Hibernate no longer provides built-in support for integrating itself with JMX environments.

JACC integration

Hibernate no longer provides built-in support for integrating itself with JACC environments.

Release artifacts

We no longer publishing zip and tgz bundles to SourceForge.

We now publish additional documentation artifacts, such as:

Previously Deprecated features

  • 'hibernate.classLoader.application', 'hibernate.classLoader.resources', 'hibernate.classLoader.hibernate' and 'hibernate.classLoader.environment': use 'hibernate.classLoaders' instead.

  • 'hibernate.hbm2dll.create_namespaces': use 'jakarta.persistence.create-database-schemas' or 'hibernate.hbm2ddl.create_namespaces'

Configuration property renames

Some configuration properties that were deprecated for a long time were finally removed for consistency reasons:

Old property New property

hibernate.ejb.metamodel.population

hibernate.jpa.metamodel.population

hibernate.ejb.cfgfile

hibernate.cfg_xml_file

hibernate.ejb.xml_files

hibernate.orm_xml_files

hibernate.hbmxml.files

hibernate.hbm_xml_files

hibernate.ejb.loaded.classes

hibernate.loaded_classes

hibernate.ejb.persistenceUnitName

hibernate.persistenceUnitName

hibernate.ejb.discard_pc_on_close

hibernate.discard_pc_on_close

hibernate.ejb.entitymanager_factory_name

hibernate.session_factory_name

hibernate.ejb.session_factory_observer

hibernate.session_factory_observer

hibernate.ejb.identifier_generator_strategy_provider

hibernate.identifier_generator_strategy_provider

There are also some property prefixes where the deprecated variant was finally removed:

Old prefix New prefix

hibernate.ejb.classcache

hibernate.classcache

hibernate.ejb.collectioncache

hibernate.collectioncache

hibernate.ejb.event

hibernate.event

We decided this is the right time since 6.0 is a major release.

Dialects

"Community" dialects moved to a separate module

As of Hibernate 6.0, some dialect classes that are maintained by vendors or individuals, as opposed to the Hibernate team. have moved to a separate Maven artifact: org.hibernate.orm:hibernate-community-dialects.

Version-specific and spatial-specific dialects are deprecated

As of Hibernate 6.0, dialects can detect and adapt to the version of the database in use and its spatial capabilities.

As a result, version-specific dialects (e.g. org.hibernate.dialect.PostgreSQL91Dialect) or spatial-specific dialects (e.g. org.hibernate.spatial.dialect.postgis.PostgisPG94Dialect) should no longer be used. Use these dialects instead, and ignore their deprecated subclasses:

  • org.hibernate.dialect.CockroachDialect

  • org.hibernate.dialect.DB2Dialect

  • org.hibernate.dialect.DB2iDialect

  • org.hibernate.dialect.DB2zDialect

  • org.hibernate.dialect.DerbyDialect

  • org.hibernate.dialect.H2Dialect

  • org.hibernate.dialect.HANAColumnStoreDialect

  • org.hibernate.dialect.HANARowStoreDialect

  • org.hibernate.dialect.HSQLDialect

  • org.hibernate.dialect.MariaDBDialect

  • org.hibernate.dialect.MySQLDialect

  • org.hibernate.dialect.OracleDialect

  • org.hibernate.dialect.PostgreSQLDialect

  • org.hibernate.dialect.PostgresPlusDialect

  • org.hibernate.dialect.SpannerDialect

  • org.hibernate.dialect.SQLServerDialect

  • org.hibernate.dialect.SQLServerDialect

  • org.hibernate.dialect.SybaseAnywhereDialect

  • org.hibernate.dialect.SybaseASEDialect

The same recommendation applies for community-supported dialects; use these and not their deprecated subclasses:

  • org.hibernate.community.dialect.CacheDialect

  • org.hibernate.community.dialect.CUBRIDDialect

  • org.hibernate.community.dialect.FirebirdDialect

  • org.hibernate.community.dialect.IngresDialect

  • org.hibernate.community.dialect.InformixDialect

  • org.hibernate.community.dialect.MaxDBDialect

  • org.hibernate.community.dialect.MimerSQLDialect

  • org.hibernate.community.dialect.RDMSOS2200Dialect

  • org.hibernate.community.dialect.SQLiteDialect

  • org.hibernate.community.dialect.TeradataDialect

  • org.hibernate.community.dialect.TimesTenDialect

Multitenancy simplification

Multitenancy in Hibernate ORM has been simplified. Hibernate will now infer whether multitenancy is enabled or not automatically:

To migrate:

  • Remove any reference to the configuration property hibernate.multiTenancy (AvailableSettings.MULTI_TENANT) or to MultiTenancyStrategy from your application: they are no longer necessary. hibernate.multiTenancy will be ignored and AvailableSettings.MULTI_TENANT/MultiTenancyStrategy will lead to compilation errors since they have been removed.

  • If your application was somehow achieving discriminator-based multitenancy through custom code (e.g. custom filters), consider relying on the new @TenantId annotation and setting the session’s tenant ID instead.


1. Think `AttributeConverter`