Hibernate 5 to 6 to 7 Migration Guide: The Breaking Changes and the Silent Ones

I have now taken three codebases through the Hibernate 5 → 6 → 7 path, and the same pattern repeated each time: the compile errors are the easy part, and the silent behaviour changes — query result types, ID generation, fetch semantics — are what reach production. This guide walks the migration in two deliberate hops (5→6, then 6→7), with the exact errors you will see at each stage and how to fix them. Do not attempt 5→7 in one jump; the diagnostics assume you pass through 6.

Tested with: Hibernate 5.6.15, 6.6.x, and 7.0.x on Java 21, against PostgreSQL 16 and H2. This guide anchors the rest of the site’s Hibernate 7 series — individual topics are linked where they go deeper.

Version and Baseline Matrix

Hibernate 5.6Hibernate 6.xHibernate 7.x
Persistence APIjavax.persistence (JPA 2.2)jakarta.persistence (3.0/3.1)jakarta.persistence (3.2)
Minimum Java81117
Spring Boot pairing2.x3.x4.x
Legacy Criteria APIDeprecatedRemovedRemoved
Session save()/update()/saveOrUpdate()StandardDeprecatedRemoved
Dialect per DB versionYes (PostgreSQL95Dialect etc.)Single dialect, runtime version detectionSame as 6

Hop 1: Hibernate 5 → 6

1.1 The jakarta Namespace

Every javax.persistence import becomes jakarta.persistence. This is mechanical but total — annotations, persistence.xml schema, JTA types. The first error you will see if anything is missed:

java.lang.ClassNotFoundException: javax.persistence.Entity
// or the subtler version when an old library leaks javax annotations:
org.hibernate.AnnotationException: Class 'com.example.Order' is not an entity

The “not an entity” variant means a dependency (an old Lombok config, a shared model JAR) still carries javax annotations that Hibernate 6 silently ignores. OpenRewrite’s jakarta recipe handles your code; auditing transitive dependencies is on you.

1.2 The Legacy Criteria API Is Gone

// BAD: Hibernate 5 native Criteria — does not compile on 6
Criteria criteria = session.createCriteria(Student.class);
criteria.add(Restrictions.eq("branch", "CE"));
List<Student> result = criteria.list();
// GOOD: JPA CriteriaBuilder (Hibernate 6/7)
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> root = cq.from(Student.class);
cq.select(root).where(cb.equal(root.get("branch"), "CE"));
List<Student> result = session.createQuery(cq).getResultList();

1.3 Silent Change: Multi-Select Query Results

In Hibernate 5, select s.name, s.branch from Student s returned List<Object[]>. Hibernate 6 returns single-element selections unwrapped and changed several tuple behaviours — code that casts to Object[] compiles fine and throws ClassCastException at runtime. Replace positional casting with constructor expressions or Tuple:

// GOOD: explicit DTO projection — immune to tuple-shape changes
List<StudentView> views = session.createQuery(
    "select new com.example.StudentView(s.name, s.branch) from Student s",
    StudentView.class).getResultList();

1.4 Silent Change: Sequence ID Allocation

Hibernate 6 changed how GenerationType.AUTO/SEQUENCE map to database sequences (per-entity sequences, allocation size 50 by default). On an existing schema created under Hibernate 5’s shared hibernate_sequence, the symptom after upgrade is either ObjectNotFoundException-style ID collisions or:

org.hibernate.exception.SQLGrammarException: could not execute statement
  [Sequence "student_seq" not found]

For existing databases, pin the old behaviour while you plan a proper sequence migration:

hibernate.id.db_structure_naming_strategy=legacy

1.5 Dialect Cleanup

Delete version-specific dialects from configuration. PostgreSQL95Dialect, MySQL57Dialect and friends are deprecated or removed; plain PostgreSQLDialect (or no dialect at all — Hibernate detects it from JDBC metadata) is correct on 6 and 7. Stale dialect settings are the most common leftover I find in migrated configs.

Hop 2: Hibernate 6 → 7

If you did hop 1 honestly — meaning you fixed deprecation warnings instead of suppressing them — hop 2 is far smaller. Hibernate 7 targets Jakarta Persistence 3.2 and Java 17+, and pairs with Spring Boot 4 (full setup in Hibernate 7 with Spring Boot 4).

2.1 save(), update(), saveOrUpdate() Are Removed

Deprecated in 6, gone in 7. The replacements are persist() and merge() — and they are not drop-in: merge() returns a new managed instance rather than managing the one you passed. The full decision table, including the duplicate-row bug this causes, is in persist, save, merge, saveOrUpdate in Hibernate 7.

// BAD: compiles on 6 with warnings, does not compile on 7
session.save(student);
session.saveOrUpdate(student);
// GOOD: Hibernate 7
session.persist(student);                  // new entities
Student managed = session.merge(student);  // detached entities — use the RETURN VALUE

2.2 get()/load() and Loading Semantics

Session.get() and load() gave way to find() and getReference(). The behavioural differences (when proxies initialize, when exceptions fire) are covered in find() vs getReference() and get() vs load() in Hibernate 7.

2.3 Bootstrap and Configuration

Hibernate 7 tightens the Jakarta-standard bootstrap. If you still build a SessionFactory from hibernate.cfg.xml, now is the moment to move to programmatic or persistence-unit bootstrap — both patterns are in SessionFactory bootstrapping without XML, and a from-scratch project skeleton is in Hibernate 7 Hello World.

The Migration Checklist

The order that worked across three codebases: (1) upgrade Java to 17/21 first, on your current Hibernate; (2) run the jakarta namespace migration; (3) upgrade to latest Hibernate 6.6.x, fix compile errors (Criteria API), then run the full test suite watching for the silent three — tuple shapes, sequence allocation, dialect issues; (4) burn down every Hibernate deprecation warning while on 6; (5) upgrade to 7, which at this point is mostly a version bump; (6) only then chase the Hibernate 7 performance features. Teams that skip step 4 turn hop 2 from a day into a sprint.

Frequently Asked Questions

Can I jump 5 → 7 directly? Technically yes, practically no: you face every breaking change simultaneously with none of Hibernate 6’s deprecation warnings to guide you. The two-hop path converts unknown runtime failures into compiler-guided fixes.

Does Spring Data JPA insulate me? Partially — repository methods hide the Session API, so the removed methods rarely hurt. The silent changes (sequences, tuples, dialects) hit Spring Data projects with full force. See Spring Data JPA: Complete Guide.

What about HQL strictness? Hibernate 6 replaced the HQL parser. Queries that leaned on lenient parsing — implicit joins in odd places, mismatched parameter types — now fail with SemanticException at startup if you enable query validation, which you should: failing at boot beats failing at 2 a.m.

AI Prompts for the Migration

Audit Breaking Usage

Scan these DAO/repository classes for Hibernate 5 APIs that break on 6/7: [paste here]. Flag legacy Criteria usage, save/update/saveOrUpdate, get/load, javax imports, and positional Object[] query result casts. Output a file-by-file fix list.

What it does: Produces the migration inventory, including the runtime-only breakages a compiler pass misses.

When to use it: Before starting, to size the work honestly.

Convert Criteria Queries

Convert this Hibernate native Criteria query to JPA CriteriaBuilder: [paste here]. Preserve exact restriction, ordering, and pagination semantics, and point out any Restrictions feature with no direct CriteriaBuilder equivalent.

What it does: Handles the most labour-intensive mechanical conversion of hop 1.

When to use it: Per query class during the Hibernate 6 upgrade.

Plan Sequence Strategy

My schema was generated by Hibernate 5 with hibernate_sequence and these entities: [paste here]. Design a migration to Hibernate 6/7 ID generation: choose between legacy naming strategy and proper per-entity sequences, and produce the SQL migration scripts for PostgreSQL.

What it does: Solves the single most dangerous silent change — ID collisions on existing data.

When to use it: Mandatory for any production database that predates Hibernate 6.

Validate HQL Compatibility

Check these HQL/JPQL queries against Hibernate 6’s stricter parser: [paste queries here]. Identify constructs the new parser rejects or interprets differently, and rewrite each in compliant form.

What it does: Pre-flights your query corpus against the rewritten parser before runtime does it for you.

When to use it: After the upgrade compiles, before the test suite runs.

Modernize Session Code

Rewrite this Hibernate 5-style persistence code for Hibernate 7: [paste here]. Replace removed Session methods with persist/merge/find/getReference, ensure merge() return values are used, and note transactional boundary assumptions that changed.

What it does: Performs the hop-2 API modernization with the merge-return-value trap handled.

When to use it: Per service/DAO class during the 6 → 7 hop.

Conclusion

The 5→6→7 path is two migrations with very different personalities: hop 1 is large and partly invisible (namespace, Criteria, tuples, sequences), hop 2 is small if and only if you cleared 6’s deprecations. Take the hops separately, treat every deprecation warning on 6 as a work item, and pin the legacy sequence strategy until you have migrated the schema deliberately. The compiler finds the loud breakages; this checklist exists for the quiet ones.

See Also

Further Reading

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.