Versions and status. Hibernate ORM 8.0.0.Beta3 (25 Sep 2026; Beta2 23 Sep, Beta1 16 Jun) and Jakarta Persistence 4.0.0-M7 (a milestone — the only 4.0 artifacts on Maven Central are M5–M7), compared with Hibernate 7.4.5.Final and Persistence 3.2.0. Spring Boot 4.1.1 / Spring Framework 7.0.9 and, for one run, Boot 4.2.0-M2 / Spring 7.1.0-M2. JDK 25.0.4, Maven 3.9.11. Nothing here is final; this article will be updated when 8.0 is released. Companion code: hibernate-demo/h8-preview.
Start with a suite you trust: 205 tests, two pre-existing errors
A migration test is only meaningful against a baseline. The companion repository’s earlier chapters ship a JUnit suite (mapping, caching, pagination, interceptors, full-text search) that runs on Hibernate 7.4.5 under Spring Boot 4.1.1. A script copies that suite to a scratch directory, swaps the Hibernate version, runs everything, and boils the result down to distinct final causes — because 159 failing tests are not 159 problems. On Hibernate 7.4.5 the baseline is 205 tests and two errors, both in one class. I did not cause those; they fail the same way when run alone, and they are a pre-existing defect in that older chapter’s fixture (listed at the end of this article).tests run: 205 failures: 0 errors: 2 skipped: 0
test classes with a problem: 1
distinct final causes (count = failing test methods):
1 org.hibernate.query.UnknownNamedQueryException: No query named 'XmlQueryEmployee.findBySalaryAboveXml'
1 org.hibernate.query.UnknownParameterException: No parameter named ':min' in query with named parameters []
Captured in suite-h7.txt.
Going deeper: how the runner works
../src and ../pom.xml to a temp directory, optionally change the Spring Boot parent version, optionally add two dependency pins, then run mvn test with -Dhibernate.version=… and -Djakarta-persistence.version=… (the two property names Spring Boot’s dependency management reads). The original tree is never modified, so the same script can compare four configurations without them contaminating each other.
A note on counts: they come from the JUnit XML-style text reports, so parameterised and nested tests can make the number differ from the one Maven prints in its own summary line.
- Runner: run-suite.sh
- Baseline: suite-h7.txt
Failure 1: Maven quietly picks an older library than Hibernate 8 asks for
First run on Hibernate 8: 202 tests, 183 problems. The number is scary and the cause is boring. The suite also uses Hibernate Search 8.4, which was built for Hibernate 7 and depends on an olderhibernate-models library (1.1.1). Hibernate 8 itself needs hibernate-models 1.3.4.
When two dependencies ask for different versions of the same library, Maven does not take the newest; it takes the one closest to your project in the dependency tree. Hibernate Search sits one level nearer than Hibernate Core, so its older copy wins. The result is a runtime error inside Hibernate itself, not a build error:
tests run: 202 failures: 2 errors: 181 skipped: 0
test classes with a problem: 67
distinct final causes (count = failing test methods):
140 (context failed earlier in the same JVM; see the first failure of that context)
39 java.lang.ExceptionInInitializerError: Exception java.lang.NoSuchFieldError: Class org.hibernate.models.spi.AnnotationTarget$Kind does not have member field 'org.hibernat
1 javax.naming.NameAlreadyBoundException: Name jdbc already bound. Use rebind() to override
1 org.opentest4j.AssertionFailedError: Expected javax.naming.NameAlreadyBoundException to be thrown, but nothing was thrown.
1 java.lang.AssertionError:
Captured in suite-h8-unpinned.txt.
<dependency><groupId>org.hibernate.models</groupId><artifactId>hibernate-models</artifactId><version>1.3.4</version></dependency>
<dependency><groupId>io.smallrye</groupId><artifactId>jandex</artifactId><version>3.6.0</version></dependency>
Source: run-suite.sh, lines 21–22 (the script writes them into a scratch copy of the POM, inside a dependencyManagement block).
The fingerprint of this bug.NoSuchFieldErrororNoSuchMethodErrornaming a class inorg.hibernate.modelsmeans two versions of the same library are on the classpath. Runmvn dependency:tree -Dincludes=org.hibernate.modelsbefore you suspect Hibernate.
Failure 2: Spring cannot host Hibernate 8 yet, and that is not something you can fix
With the classpath fixed, 159 tests still have a problem. 140 of them only report that a Spring context failed earlier; among the tests that show their own cause, the biggest group has one. Jakarta Persistence 4.0 adds six abstract methods toPersistenceUnitInfo, the interface a container uses to describe a persistence unit to the provider. Hibernate 8 calls them. Spring’s implementation, SpringPersistenceUnitInfo, does not have them, so the entity manager factory cannot be created at all:
tests run: 205 failures: 1 errors: 158 skipped: 0
test classes with a problem: 52
distinct final causes (count = failing test methods):
140 (context failed earlier in the same JVM; see the first failure of that context)
14 java.lang.NoSuchMethodException: org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.getAllPackageDescriptors()
4 org.hibernate.query.NamedQueryValidationException: Errors in named queries:
1 java.lang.AssertionError:
Captured in suite-h8-pinned.txt.
# persistence-spi (javap on jakarta.persistence.spi.PersistenceUnitInfo, and on Spring's implementation)
--- methods that exist in Jakarta Persistence 4.0.0-M7 but not in 3.2.0
public abstract FetchType getDefaultToOneFetchType();
public abstract List<String> getAllClassNames();
public abstract List<String> getAllModuleDescriptors();
public abstract List<String> getAllPackageDescriptors();
public abstract List<String> getManagedModuleDescriptors();
public abstract List<String> getManagedPackageDescriptors();
--- org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 7.0.9: methods mentioning those names
class found, 15 methods and constructors listed; matches for the six new names: 0
--- org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo 7.1.0-M2: methods mentioning those names
class found, 15 methods and constructors listed; matches for the six new names: 0
Captured in 06-persistence-spi.txt.
What this means. A Spring Boot application cannot run Hibernate 8 today, whatever you put in the POM. The fix has to land in Spring Framework (probably 7.1 or later). If you use Spring Data JPA, keep an eye on the Framework milestones, not on Hibernate’s. Hibernate used without Spring is the only setup the rest of this article can exercise.
Going deeper: what the 140, the 14 and the 4 are
getAllPackageDescriptors(), 4 come from the named-query validation described in the last panel of this article, and 1 is the AssertionError below.
The one AssertionError in the list is a test that inspected XML mapping metadata; I did not chase it further, so treat it as unexplained.
- All four suite runs: suite-h8-unpinned.txt, suite-h8-pinned.txt, suite-h8-boot42.txt
- Jakarta Persistence 4.0 milestone builds: Maven Central
Failure 3: code that compiled on Hibernate 7 no longer does
Everything so far was infrastructure. This section is about your code. Five tiny files, none in the normal build, are compiled against each Hibernate and the compiler’s words are kept. First, APIs that Hibernate 7 already marked deprecated and Hibernate 8 has removed. Here the same file gives two warnings on 7 and a hard error on 8: public static class Dto {
public Integer id;
}
Object rows(Session s) {
return s.createNativeQuery("select 1 as id", Object.class).unwrap(NativeQuery.class)
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
}
}
Source: UsesTransformers.java, lines 8–17.
--- UsesTransformers on h8
UsesTransformers.java:3: error: package org.hibernate.transform does not exist
import org.hibernate.transform.Transformers;
UsesTransformers.java:14: error: cannot find symbol
.setResultTransformer(Transformers.aliasToBean(Dto.class)).list();
symbol: variable Transformers
location: class UsesTransformers
2 errors
Captured in 04-compile-errors.txt.
Result transformers (org.hibernate.transform) were the way to map a native query onto a DTO; the whole package is gone. Session.replicate and ReplicationMode fail the same way (lines 26–35 of the transcript). A different kind of break is a changed return type. A bulk-loading line many projects contain keeps the generated id:
Object load(StatelessSession ss) {
Object id = ss.insert(new com.ankurm.h8.Author("A", "[email protected]"));
return id;
}
Source: StatelessInsertReturnsId.java, lines 6–9.
--- StatelessInsertReturnsId on h7
--- StatelessInsertReturnsId on h8
StatelessInsertReturnsId.java:7: error: incompatible types: void cannot be converted to Object
Object id = ss.insert(new com.ankurm.h8.Author("A", "[email protected]"));
1 error
Captured in 04-compile-errors.txt.
Read this one twice. A method whose return type changes is worse than one that disappears: the line above still looks perfectly normal. Grep forStatelessSessionand any code that uses the value returned byinsert(..).
Going deeper: the removal list, and what I did not compile
Session.replicate() with ReplicationMode; the whole org.hibernate.transform package; the lock cascade style; the @Proxy and @Polymorphism XML attributes; OSGi manifest metadata; and the relocation POMs under the old org.hibernate group id. I compiled the first two and the StatelessSession change. The rest come from the guide, not from a run.
The change that does not show up in your code: binary compatibility
A very common line still compiles on Hibernate 8, with only a warning:Query<Author> q = session.createQuery(hql, Author.class). That is source compatibility. It is not binary compatibility: the return type of Session.createQuery(String, Class) changed underneath, and the compiled call site records the return type. The probe below is compiled against one Hibernate and run on the other, in both directions.
int count(Session s) {
Query<com.ankurm.h8.Author> q = s.createQuery("from Author", com.ankurm.h8.Author.class);
return q.getResultList().size();
}
}
Source: QueryVariable.java, lines 7–11.
--- QueryVariable on h8
QueryVariable.java:9: warning: [removal] getResultList() in Query has been deprecated and marked for removal
return q.getResultList().size();
where T is a type-variable:
T extends Object declared in interface Query
1 warning
Captured in 04-compile-errors.txt.
# binary-compatibility (BinaryProbe compiled against one Hibernate, run on the other)
--- compiled on h7, run on h8
Exception in thread "main" java.lang.NoSuchMethodError: 'org.hibernate.query.Query org.hibernate.Session.createQuery(java.lang.String, java.lang.Class)'
--- compiled on h8, run on h7
Exception in thread "main" java.lang.NoSuchMethodError: 'org.hibernate.query.SelectionQuery org.hibernate.Session.createQuery(java.lang.String, java.lang.Class)'
Captured in 05-binary-compatibility.txt.
Why this matters more than the compile errors. You can fix your own source. You cannot recompilespring-data-jpa, an in-house library, or any vendor jar built against Hibernate 7. Every such jar that callscreateQuery(String, Class)throwsNoSuchMethodErroron Hibernate 8 until its authors rebuild it. That, not your entity code, sets your real migration date.
What changed at runtime, and what the migration guide says that I could not reproduce
The migration guide lists behaviour changes. Three of them can be observed with plain Hibernate, so I ran identical code on both versions. Two behaviours differ. The rest did not. Flush order. Hibernate 8 sorts pending inserts by their foreign-key graph. I persisted a parent, its child, another parent and its child, in that order, and recorded the order in which Hibernate prepares INSERT statements: private static List<String> run(Map<String, String> settings) {
Db.SQL.clear();
try (SessionFactory sf = Db.open(settings, Parent.class, Child.class)) {
Db.SQL.clear();
sf.inTransaction(s -> {
Parent p1 = new Parent("p1");
Parent p2 = new Parent("p2");
s.persist(p1);
s.persist(new Child("c1", p1));
s.persist(p2);
s.persist(new Child("c2", p2));
});
return Db.SQL.stream().filter(q -> q.startsWith("insert")).map(q -> q.split(" ")[2]).toList();
}
}
Source: FlushOrderTest.java, lines 13–27.
# 02-flush-order (Hibernate ORM 7.4.5.Final, JDK 25)
default : [Parent, Child, Parent, Child]
hibernate.flush.queue.type=legacy: [Parent, Child, Parent, Child]
hibernate.flush.queue.type=graph : [Parent, Child, Parent, Child]
Captured in 02-flush-order-h7.txt.
# 02-flush-order (Hibernate ORM 8.0.0.Beta3, JDK 25)
default : [Parent, Child]
hibernate.flush.queue.type=legacy: [Parent, Child, Parent, Child]
hibernate.flush.queue.type=graph : [Parent, Child]
Captured in 02-flush-order-h8.txt.
On 7 the four statements alternate parent, child, parent, child, whatever the setting. On 8 the default groups them (parents together, then children), andhibernate.flush.queue.type=legacy brings the old alternating order back. The effect is fewer switches between tables, but SQL logs and any test that asserts statement order will change.
A mutating @NamedQuery. The guide says named queries that modify data must use the new @NamedStatement. This entity has a JPA @NamedQuery whose text is an update:
/** Not registered by default: a named query that is an UPDATE. Hibernate 8 wants @NamedStatement for this. */
@Entity
@NamedQuery(name = "Mutating.rename", query = "update MutatingNamedQueryEntity m set m.name = :name")
public class MutatingNamedQueryEntity {
@Id
Long id;
String name;
}
Source: MutatingNamedQueryEntity.java, lines 7–16.
# 03-mutating-named-query (Hibernate ORM 7.4.5.Final, JDK 25)
session factory built; createNamedQuery(..).executeUpdate() returned 0
Captured in 03-mutating-named-query-h7.txt.
# 03-mutating-named-query (Hibernate ORM 8.0.0.Beta3, JDK 25)
threw java.lang.IllegalArgumentException <- org.hibernate.query.IllegalSelectQueryException :: Expecting a SELECT Query [org.hibernate.query.sqm.tree.spi.select.SqmSelectStatement], but found org.hibernate.query.sqm.tree.spi.update.SqmUpdateStatement [update MutatingNamedQueryEntity m set m.name = :name]
Captured in 03-mutating-named-query-h8.txt.
Hibernate 7 accepts it and runs the update. Hibernate 8 refuses to build theSessionFactory, so this is a startup failure, not something a test hits later. Any application with a modifying @NamedQuery will not start until it moves to @NamedStatement.
Exceptions. The guide lists several exception-type changes. Five ordinary situations, same code, both versions:
# 01-exception-contracts (Hibernate ORM 7.4.5.Final, JDK 25)
unbound parameter : threw org.hibernate.QueryParameterException
null parameter : returned 0
type mismatch : threw org.hibernate.query.QueryArgumentException
unique violation : threw org.hibernate.exception.ConstraintViolationException <- org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
unknown parameter : threw java.lang.IllegalArgumentException <- org.hibernate.query.UnknownParameterException
named query, no row: 0 rows
Captured in 01-exception-contracts-h7.txt.
# 01-exception-contracts (Hibernate ORM 8.0.0.Beta3, JDK 25)
unbound parameter : threw org.hibernate.QueryParameterException
null parameter : returned 0
type mismatch : threw org.hibernate.query.QueryArgumentException
unique violation : threw org.hibernate.exception.ConstraintViolationException <- org.h2.jdbc.JdbcBatchUpdateException
unknown parameter : threw java.lang.IllegalArgumentException <- org.hibernate.query.UnknownParameterException
named query, no row: 0 rows
Captured in 01-exception-contracts-h8.txt.
| Guide says | I observed on 8.0.0.Beta3 |
|---|---|
Null query parameter throws IllegalStateException | setParameter("name", null) returned 0 rows, same as 7 |
Unique-constraint violation throws EntityExistsException | ConstraintViolationException, same class as 7 (the root cause is H2’s batch exception on 8, its integrity-constraint exception on 7) |
Type mismatch throws IllegalArgumentException | QueryArgumentException, same class as 7 — I did not check its supertype |
Going deeper: two more behaviours the suite exposed
orm.xml. The older chapters keep a META-INF/orm.xml whose named queries refer to one entity. Persistence units bootstrapped outside Spring (four tests in the suite) load it automatically. On 7.4.5 they start. On 8.0.0.Beta3 they fail at startup with NamedQueryValidationException: Could not resolve root entity, because those units do not list that entity. I read that as Hibernate 8 validating more strictly, but I did not confirm it against the release notes, so it is an observation, not an explanation.
Hibernate Search. Search 8.4 is built for Hibernate 7; the suite’s search tests fail for the reasons above. Expect a Search release aligned with Hibernate 8 before you can test them.
- Suite results: suite-h8-pinned.txt
From the migration guide, not tested here
These are in the guide and I did not exercise them. They are listed so you know what to look for; each is a claim from the guide, not from a run.| Area | Change (migration guide) |
|---|---|
| Java time | hibernate.type.java_time_use_direct_jdbc defaults to true |
| IDs | @NativeGenerator default allocation size changes from 1 to 50 |
| Locking | Lock cascading removed; explicit locking required |
| Large objects | Blob/Clob access requires an active transaction |
| Mapping | hibernate.mapping.default_list_semantics replaced by @DefaultListSemantics; @OptimisticLock replaced by @ExcludedFromVersioning |
| Sessions | Re-entering the session from a callback throws IllegalStateException |
| Micrometer | micrometer-core is no longer a transitive dependency of hibernate-micrometer |
Should you test Hibernate 8 now?
Yes, but only the part you control. Compile your code against 8.0.0.Beta3 to size the source changes, and check every third-party jar that touches Hibernate for a release compiled against 8. Do not try to run a Spring Boot application on it: that is blocked until Spring implements the new Persistence SPI. And do not migrate on a beta — the behaviours above are exactly the kind that move between Beta3 and the final release.
Pre-existing defects noticed on the way
Reported separately because they are not part of the Hibernate 8 story.OrmXmlNamedQueryTestin the mainhibernate-demosuite has two errors on Hibernate 7.4.5 too (UnknownNamedQueryExceptionforXmlQueryEmployee.findBySalaryAboveXml; reproduces when the class is run alone).
Further reading
- Companion code: hibernate-demo/h8-preview; older chapters in the same repository
- Hibernate ORM 8.0 migration guide and the 8.0 series page
- What’s new in Hibernate 8.0
- Related on this site: Spring Boot 4.2 preview: what to test now
No Comments yet!