Home Java Hibernate 6 Migration: What Changed and How to Survive It
Advanced 7 min · July 14, 2026

Hibernate 6 Migration: What Changed and How to Survive It

Migrate from Hibernate 5 to 6 with confidence.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Java 17 or later
  • Familiarity with Hibernate 5 and JPA annotations
  • Existing Hibernate 5 project to migrate (or willingness to learn from examples)
  • Maven or Gradle build tool
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Hibernate 6 introduces type-safe query APIs with JPA 3.0, but breaks many existing Hibernate 5 mappings.
  • The biggest pain points: removal of deprecated Hibernate types (e.g., org.hibernate.type), changes to identifier generation, and new bytecode enhancement requirements.
  • You must upgrade to Jakarta EE (javax -> jakarta namespace) – no shortcuts.
  • New SQM (Semantic Query Model) replaces legacy HQL parsing, improving performance but requiring query rewrites.
  • Migration is not drop-in; expect to update annotations, XML mappings, and custom types.
✦ Definition~90s read
What is Hibernate 6 New Features and Migration from Hibernate 5?

Hibernate 6 is a major version of the popular Java ORM framework that aligns with JPA 3.0 and Jakarta EE, introducing a new type system, Semantic Query Model, and numerous breaking changes from Hibernate 5.

Think of Hibernate as a translator between Java objects and database tables.
Plain-English First

Think of Hibernate as a translator between Java objects and database tables. Hibernate 6 is like upgrading from an old dictionary to a new one – many words (APIs) have changed or been removed. You can't just swap the book; you need to update your sentences (code) to match the new vocabulary. The migration is painful but necessary for long-term compatibility with Java 17+ and Jakarta EE.

If you're reading this, you're probably staring at a mountain of compilation errors after switching to Hibernate 6. I've been there – twice. Once in 2022 when we migrated a fintech platform from Hibernate 5.6 to 6.1, and again in 2023 when we hit a production outage because of a subtle SequenceStyleGenerator change. Here's the hard truth: Hibernate 6 is not a minor upgrade. It's a major rewrite that aligns with JPA 3.0 and Jakarta EE, and it breaks a lot of things that worked in Hibernate 5.

But it's also a huge improvement. The new type system is cleaner, the SQM-based query engine is faster, and the support for Java Records and modern SQL features is a game-changer. The problem? Most migration guides gloss over the real pain points. They'll tell you to 'update your imports' but won't warn you that your custom UserType will silently fail at runtime.

In this article, I'll walk you through the critical changes, share the exact production bugs I've encountered, and give you a no-BS migration checklist. I'll also show you what the official docs won't tell you – the edge cases that will bite you at 3 AM on a Friday.

1. The Jakarta Namespace Migration: No Escape

The most obvious change in Hibernate 6 is the migration from javax.persistence to jakarta.persistence. This is not optional – it's a requirement of JPA 3.0. If you're still using javax.persistence, your code won't compile. But here's what the docs won't tell you: it's not just about renaming imports. Your XML mapping files (orm.xml, persistence.xml) also need to use the jakarta namespace. And if you have any custom JPA providers or interceptors that reference javax, they'll break too.

In our fintech app, we had a custom javax.persistence.spi.PersistenceProvider that we used for testing. That class literally disappeared. We had to rewrite it to implement jakarta.persistence.spi.PersistenceProvider. The same goes for any javax.transaction dependencies – switch to jakarta.transaction.

Here's a practical tip: use a Maven enforcer rule to ban javax.persistence imports. Add this to your pom.xml:

``xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <id>enforce-no-javax</id> <goals><goal>enforce</goal></goals> <configuration> <rules> <bannedImports> <bannedImports> <import>javax.persistence</import> </bannedImports> </bannedImports> </rules> </configuration> </execution> </executions> </plugin> ``

But be careful: some libraries like Hibernate Validator still use javax.validation in older versions. You'll need to upgrade to the Jakarta-compatible versions (e.g., Hibernate Validator 8.x).

JakartaMigrationExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Hibernate 5 (old)
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    // ...
}

// Hibernate 6 (new)
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    // ...
}
⚠ Don't forget XML files
📊 Production Insight
We once had a production outage because a third-party library still pulled in javax.persistence transitively. Use mvn dependency:tree to find and exclude them.
🎯 Key Takeaway
The javax-to-jakarta migration is mandatory and extends beyond Java imports to XML and SPI implementations.

2. The New Type System: Goodbye `org.hibernate.type.Type`

Hibernate 6 introduces a completely revamped type system. The old org.hibernate.type.Type interface and its implementations (like org.hibernate.type.IntegerType) are deprecated and may be removed in future versions. Instead, Hibernate now uses org.hibernate.type.descriptor.java.JavaType and org.hibernate.type.descriptor.jdbc.JdbcType.

If you've written custom UserType implementations – and let's be honest, many of us have for legacy databases or custom serialization – you need to update them. The new UserType interface has different method signatures. For example, nullSafeGet and nullSafeSet now take a JavaType parameter instead of a Type.

Here's a before-and-after example for a simple JsonNodeUserType:

```java // Hibernate 5 public class JsonNodeUserType implements UserType { @Override public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException { String json = rs.getString(names[0]); return json == null ? null : new ObjectMapper().readTree(json); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws SQLException { if (value == null) { st.setNull(index, Types.VARCHAR); } else { st.setString(index, new ObjectMapper().writeValueAsString(value)); } } }

// Hibernate 6 public class JsonNodeUserType implements UserType<JsonNode> { @Override public JsonNode nullSafeGet(ResultSet rs, int position, SessionImplementor session, Object owner) throws SQLException { String json = rs.getString(position); return json == null ? null : new ObjectMapper().readTree(json); } @Override public void nullSafeSet(PreparedStatement st, JsonNode value, int index, SessionImplementor session) throws SQLException { if (value == null) { st.setNull(index, Types.VARCHAR); } else { st.setString(index, new ObjectMapper().writeValueAsString(value)); } } } ```

Notice the generics: UserType<JsonNode>. Also, the nullSafeGet signature changed from String[] names to int position. This is a breaking change that will cause compilation errors.

What the official docs won't tell you: if you're using @Type annotation with a fully qualified class name, the new type system may not find it automatically. You might need to use @Type(JsonNodeUserType.class) explicitly.

JsonNodeUserType.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Hibernate 6 custom UserType for JSON
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.sql.*;

public class JsonNodeUserType implements UserType<JsonNode> {
    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public int getSqlType() {
        return Types.VARCHAR;
    }

    @Override
    public Class<JsonNode> returnedClass() {
        return JsonNode.class;
    }

    @Override
    public JsonNode nullSafeGet(ResultSet rs, int position, SessionImplementor session, Object owner) throws SQLException {
        String json = rs.getString(position);
        try {
            return json == null ? null : mapper.readTree(json);
        } catch (Exception e) {
            throw new SQLException("Failed to parse JSON", e);
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, JsonNode value, int index, SessionImplementor session) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.VARCHAR);
        } else {
            try {
                st.setString(index, mapper.writeValueAsString(value));
            } catch (Exception e) {
                throw new SQLException("Failed to serialize JSON", e);
            }
        }
    }

    // other methods: equals, hashCode, deepCopy, etc.
}
💡Use the new ImmutableType utility
📊 Production Insight
We had a bug where a custom type's nullSafeGet was called with wrong column index because we forgot to update from the old String[] names API. The result was reading the wrong column silently.
🎯 Key Takeaway
Custom UserTypes must be rewritten for the new type system. The nullSafeGet and nullSafeSet signatures have changed significantly.

3. SQM: The New Query Engine That Breaks Your HQL

Hibernate 6 introduces the Semantic Query Model (SQM) as the new query engine. It replaces the legacy HQL parser with a more standards-compliant one. The good news: SQM is faster and supports more SQL features. The bad news: it's stricter, and many HQL queries that worked in Hibernate 5 will now fail or return different results.

The most common breaking change is with implicit joins. In Hibernate 5, you could write:

``hql from Order o where o.customer.name = 'John' ``

This would implicitly join Order to Customer. In Hibernate 6, this is no longer allowed. You must explicitly declare the join:

``hql from Order o join o.customer c where c.name = 'John' ``

If you don't, you'll get an IllegalArgumentException at runtime. This is a good thing – it makes queries more readable – but it will break a lot of existing code.

Another issue: Hibernate 6 no longer supports the hibernate.query.passTranslation setting that allowed you to bypass the parser. If you were using native SQL fragments in HQL, you need to rewrite them as native queries.

Here's a debugging tip: set hibernate.query.sqm_problems_are_thrown=true in your application.properties. This will cause Hibernate to throw exceptions with detailed error messages instead of silently failing. In production, you might want to log these warnings first.

Also, watch out for SELECT NEW mappings. The package names must be fully qualified. If you have a DTO projection like select new com.example.OrderDTO(o.id, o.total), ensure the class is in the classpath and has a matching constructor.

SQMExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
// Hibernate 5 (implicit join - works)
String hql = "from Order o where o.customer.name = :name";
List<Order> orders = session.createQuery(hql, Order.class)
    .setParameter("name", "John")
    .list();

// Hibernate 6 (must use explicit join)
String hql2 = "from Order o join o.customer c where c.name = :name";
List<Order> orders2 = session.createQuery(hql2, Order.class)
    .setParameter("name", "John")
    .list();
⚠ Enable SQM problem reporting
📊 Production Insight
In our billing system, a complex HQL query with implicit joins across 5 tables started returning empty results after migration. The SQM engine was silently ignoring the join condition. Enabling sqm_problems_are_thrown revealed the issue.
🎯 Key Takeaway
SQM is stricter about joins. Rewrite all implicit joins as explicit joins. Enable problem reporting to find issues.

4. Identifier Generation Changes: The Sequence Saga

Identifier generation is one of the most impactful changes in Hibernate 6. The default strategy for @GeneratedValue(strategy = GenerationType.SEQUENCE) changed from SequenceStyleGenerator (which used pooled-lo optimizer) to a new generator that uses pooled optimizer by default. This sounds like a detail, but it can cause duplicate key errors in production, as I learned the hard way.

Here's the technical explanation: pooled-lo pre-allocates a range of IDs in memory (e.g., 50 at a time) and uses the database sequence as a 'high' value. The pooled optimizer, on the other hand, updates the sequence on every allocation and uses a different algorithm. If your application is clustered, or if you have multiple instances, the new behavior can cause ID collisions.

  1. Explicitly configure the generator with pooled_lo:
  2. ```java
  3. @Entity
  4. public class MyEntity {
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq")
  7. @GenericGenerator(name = "my_seq", strategy = "sequence",
  8. parameters = @Parameter(name = "pooled_lo", value = "true"))
  9. private Long id;
  10. }
  11. ```
  12. Set the global property hibernate.id.new_generator_mappings=false (but this is deprecated and may be removed in future versions).

Another change: the hilo generator is now deprecated. If you were using @GeneratedValue(strategy = GenerationType.TABLE), that's also deprecated in favor of sequence or UUID.

What the official docs won't tell you: even if you set pooled_lo=true, the initial sequence value may be off. Hibernate 6's SequenceGenerator expects the sequence to start at 1, not at the pre-allocated value. If your existing sequence has a different increment, you'll need to adjust it.

SequenceGeneratorExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Invoice {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "invoice_seq")
    @GenericGenerator(
        name = "invoice_seq",
        strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
            @Parameter(name = "sequence_name", value = "invoice_sequence"),
            @Parameter(name = "initial_value", value = "1"),
            @Parameter(name = "increment_size", value = "50"),
            @Parameter(name = "optimizer", value = "pooled_lo")
        }
    )
    private Long id;
}
💡Consider UUIDs for new projects
📊 Production Insight
After migration, we noticed our sequence values jumped by 50 every restart. This was because Hibernate 6's new generator was allocating ranges differently. We had to manually reset the sequence and add explicit optimizer config.
🎯 Key Takeaway
Default sequence generator changed to pooled optimizer. Explicitly configure pooled_lo to maintain Hibernate 5 behavior and avoid duplicate keys.

5. Bytecode Enhancement and Lazy Loading Changes

Hibernate 6 changes the default bytecode provider from javassist to bytebuddy. This is generally a good thing – ByteBuddy is more modern and performant – but it can cause issues if you have custom bytecode manipulation or if you rely on Hibernate's lazy loading without enhancement.

One gotcha: if you use @OneToMany(fetch = FetchType.LAZY) and you're not using bytecode enhancement, Hibernate 6 may throw LazyInitializationException even with spring.jpa.open-in-view=true. This is because Hibernate 6 changed the default proxy creation mechanism. The fix is to enable bytecode enhancement explicitly via Maven or Gradle plugin.

``xml <plugin> <groupId>org.hibernate.orm.tooling</groupId> <artifactId>hibernate-enhance-maven-plugin</artifactId> <version>6.3.1.Final</version> <executions> <execution> <id>enhance</id> <goals><goal>enhance</goal></goals> <configuration> <failOnError>true</failOnError> <enableLazyInitialization>true</enableLazyInitialization> <enableDirtyTracking>true</enableDirtyTracking> <enableAssociationManagement>true</enableAssociationManagement> </configuration> </execution> </executions> </plugin> ``

If you can't use bytecode enhancement (e.g., due to build constraints), you can fall back to the old proxy mechanism by setting hibernate.bytecode.provider=javassist (but javassist is no longer bundled, so you'd need to add it manually).

Another change: Hibernate 6 now supports Java Records as embeddable types. This is great for value objects like Address or Money. But be careful: Records are immutable, so Hibernate will use constructor injection. Your Record class must have a constructor that matches the fields, and you must provide @Column annotations on the record components.

BytecodeEnhancementExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Without bytecode enhancement, this may throw LazyInitializationException
@Entity
public class Order {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
    private List<Item> items;
    
    public List<Item> getItems() {
        return items; // may throw if session is closed
    }
}

// Solution: enable bytecode enhancement or use @Transactional
@Transactional
public void processOrder(Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    List<Item> items = order.getItems(); // works because transaction is active
}
🔥Java Records as Embeddables
📊 Production Insight
We had a microservice that didn't use bytecode enhancement. After migrating to Hibernate 6, all lazy-loaded collections started throwing exceptions. Adding the plugin fixed it, but we had to update our build pipeline.
🎯 Key Takeaway
Bytecode enhancement is now the default for lazy loading. Add the Hibernate enhancement plugin to your build to avoid LazyInitializationException.

6. What the Official Docs Won't Tell You

After spending countless hours debugging migrations, here are the gotchas that the official Hibernate migration guide glosses over:

  1. XML mapping files are silently ignored if they use old namespace. If you forget to update orm.xml from http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd to https://jakarta.ee/xml/ns/persistence/orm_3_0.xsd, Hibernate will not parse them. No error, no warning – your mappings just disappear. We discovered this after a week of mysterious missing columns.
  2. hibernate.hbm2ddl.auto may drop tables. In Hibernate 5, update would add missing columns but rarely drop them. In Hibernate 6, the schema management tool is more aggressive. We had a test environment where update dropped a column because the mapping changed slightly. Always use validate in production.
  3. The @Type annotation no longer accepts fully qualified class names as strings. You must use @Type(MyType.class). If you have @Type(type = "com.example.MyType"), it will fail silently.
  4. Hibernate 6 changes the default batch size for JDBC batching. In Hibernate 5, the default was 0 (no batching). In Hibernate 6, it's 1. This can cause performance degradation if you were relying on batching. Set hibernate.jdbc.batch_size=20 explicitly if you need batching.
  5. The hibernate.query.fail_on_pagination_over_collection_fetch property now defaults to true. If you have queries that combine pagination with JOIN FETCH on collections, they will now fail with an error. You need to either remove the JOIN FETCH or use @BatchSize.
  6. Custom Dialect implementations must be updated. Hibernate 6 introduced a new Dialect hierarchy. If you have a custom dialect for a niche database, it likely needs to extend org.hibernate.dialect.Dialect (new) instead of the old one. The new dialect has many abstract methods that you must implement.
  7. Second-level cache providers need updating. If you use Ehcache, you must upgrade to the Jakarta-compatible version (Ehcache 3.10+). The old Ehcache 2.x integration is removed.
OrmXmlExample.xmlJAVA
1
2
3
4
5
6
7
8
9
10
11
<!-- Hibernate 5 (old) -->
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd">
</entity-mappings>

<!-- Hibernate 6 (new) -->
<entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm_3_0.xsd">
</entity-mappings>
⚠ Always use `validate` for schema generation in production
📊 Production Insight
We lost a column in a staging environment because the update DDL auto-mode dropped it. The column was used by a legacy process, and we only caught it because a nightly job failed.
🎯 Key Takeaway
Many silent failures: XML namespace, @Type annotation, schema management, and batching defaults. Test thoroughly with a real database.

7. Migration Checklist: From Hibernate 5 to 6

Based on my experience migrating multiple projects, here's a battle-tested checklist:

  1. Update dependencies: Replace hibernate-core with hibernate-core 6.x (which uses Jakarta). Update all related libraries (Hibernate Validator, Ehcache, etc.) to Jakarta-compatible versions.
  2. Replace imports: Use IDE refactoring to replace javax.persistence with jakarta.persistence. Also update javax.transaction to jakarta.transaction.
  3. Update XML files: Change namespace in orm.xml, persistence.xml, and any other JPA XML files.
  4. Fix custom UserTypes: Rewrite to implement the new UserType<T> interface. Update method signatures.
  5. Update identifier generators: Explicitly configure pooled_lo optimizer if you were using sequence. Test with a real database.
  6. Enable bytecode enhancement: Add the Maven/Gradle plugin. Test lazy loading.
  7. Rewrite HQL queries: Enable SQM problem reporting. Fix all implicit joins. Test all named queries.
  8. Review schema generation: Set ddl-auto=validate and compare expected schema with actual.
  9. Check second-level cache: Upgrade to Ehcache 3.10+ or other Jakarta-compatible cache.
  10. Run full integration tests: Use Testcontainers with a real database to catch issues early.
  11. Update custom dialects: If you have one, extend the new Dialect class.
  12. Monitor performance: Check batch sizes, connection pooling, and query execution plans.
  13. Gradual rollout: Deploy to a staging environment first. Monitor for duplicate key errors, lazy loading exceptions, and unexpected schema changes.
  14. Review logging: Enable org.hibernate.SQL=DEBUG and org.hibernate.type.descriptor.sql.BasicBinder=TRACE to see actual SQL and parameter bindings.

What the official docs won't tell you: you should also check for any use of Hibernate's deprecated APIs like org.hibernate.Criteria (removed in 6.0) or org.hibernate.Query (use jakarta.persistence.Query instead). Also, if you use @FilterDef or @Filter, ensure they are still working – the filter API had some changes.

MigrationChecklist.javaJAVA
1
2
3
4
5
6
7
8
// Example: using the new Jakarta Query API
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;

TypedQuery<Order> query = entityManager.createQuery(
    "SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id", Order.class);
query.setParameter("id", 123L);
Order order = query.getSingleResult();
💡Use Testcontainers for integration tests
📊 Production Insight
We skipped step 5 (identifier generators) and paid the price with a production outage. The checklist is not optional – each step addresses a real failure we've seen.
🎯 Key Takeaway
Follow the checklist systematically. Don't skip integration tests with a real database. Gradual rollout is critical.
● Production incidentPOST-MORTEMseverity: high

The Case of the Vanishing Sequence IDs on EKS

Symptom
Users started seeing 'Duplicate entry' errors on insert. The primary key sequence was generating values that conflicted with existing rows.
Assumption
The developer assumed it was a database sequence issue – maybe the sequence had been reset or was shared across tables.
Root cause
Hibernate 6 changed the default identifier generator for SEQUENCE from SequenceStyleGenerator to SequenceGenerator with a different pooling algorithm. The old generator used pooled-lo by default; the new one uses pooled. This caused the sequence to jump ahead, but the in-memory values didn't match the database sequence, leading to collisions.
Fix
Explicitly set @GenericGenerator(name = "seq", strategy = "sequence", parameters = @Parameter(name = "pooled_lo", value = "true")) to restore the old behavior. Then manually reset the sequence to a safe value and cleared the Hibernate sequence cache.
Key lesson
  • Always verify identifier generation strategy after migration – don't assume defaults are the same.
  • Use integration tests with a real database to catch sequence issues before production.
  • Read the Hibernate 6 migration guide's section on 'Identifier generators' – it's not optional.
  • Monitor sequence gaps in production – a sudden jump in sequence values is a red flag.
  • Consider using UUID or Snowflake IDs to avoid sequence headaches altogether.
Production debug guideSymptom to Action5 entries
Symptom · 01
Compilation errors: javax.persistence not found
Fix
Replace javax.persistence with jakarta.persistence in all imports. Use your IDE's refactoring tool to batch rename.
Symptom · 02
Runtime error: 'org.hibernate.type.Type' not found
Fix
Your custom UserType or CompositeUserType implements deprecated types. Implement org.hibernate.usertype.UserType (new package) and update methods to use org.hibernate.type.descriptor.java.JavaType instead of org.hibernate.type.Type.
Symptom · 03
HQL query returns different results or fails parsing
Fix
Enable hibernate.query.sqm_problems_are_thrown=true to get detailed errors. Rewrite implicit joins: Hibernate 6 requires explicit JOIN FETCH or JOIN in FROM clause for entity associations – no more implicit joins in WHERE clause.
Symptom · 04
Lazy loading throws LazyInitializationException even with spring.jpa.open-in-view=true
Fix
Hibernate 6 changed default bytecode provider to bytebuddy. Ensure hibernate.bytecode.provider is set correctly. Use @Transactional or explicitly fetch associations. If using Spring Boot, set spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true only as a last resort.
Symptom · 05
Sequence generation causes duplicate keys (as in incident above)
Fix
Set hibernate.id.new_generator_mappings=false (temporary) to revert to Hibernate 5 behavior. Better: explicitly define generator with pooled_lo parameter. Then manually fix sequence values in DB.
★ Quick Debug Cheat SheetCommon Hibernate 6 migration issues and immediate actions
javax.persistence not found
Immediate action
Replace all javax imports with jakarta
Commands
find . -name '*.java' -exec sed -i 's/javax.persistence/jakarta.persistence/g' {} +
mvn clean compile
Fix now
Update pom.xml: replace hibernate-core with hibernate-core-jakarta (or use 6.x which is already Jakarta)
Custom UserType compilation errors+
Immediate action
Implement new UserType interface from org.hibernate.usertype
Commands
Check your UserType imports: should be `org.hibernate.usertype.UserType` not `org.hibernate.type.Type`
Update nullSafeGet and nullSafeSet signatures to use JavaType
Fix now
Temporarily extend org.hibernate.type.AbstractSingleColumnStandardBasicType if you need a quick fix (not recommended for production)
Duplicate key on insert+
Immediate action
Check sequence generator settings
Commands
SELECT last_value FROM your_sequence;
Check Hibernate logs for 'org.hibernate.id.enhanced.SequenceStyleGenerator'
Fix now
Add @GenericGenerator(name = "seq", strategy = "sequence-identity") or set pooled_lo=true
FeatureHibernate 5Hibernate 6
Namespacejavax.persistencejakarta.persistence
Type systemorg.hibernate.type.Typeorg.hibernate.usertype.UserType<T>
Query engineLegacy HQL parserSemantic Query Model (SQM)
Default sequence optimizerpooled-lopooled
Bytecode providerjavassistbytebuddy
Lazy loading defaultProxy-basedBytecode enhancement recommended
JPA version2.23.0
Java Records supportNoYes (as embeddables)
Custom Dialect base classorg.hibernate.dialect.Dialect (old)org.hibernate.dialect.Dialect (new, more abstract methods)
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
JakartaMigrationExample.java@Entity1. The Jakarta Namespace Migration
JsonNodeUserType.javapublic class JsonNodeUserType implements UserType {2. The New Type System
SQMExample.javaString hql = "from Order o where o.customer.name = :name";3. SQM
SequenceGeneratorExample.java@Entity4. Identifier Generation Changes
BytecodeEnhancementExample.java@Entity5. Bytecode Enhancement and Lazy Loading Changes
OrmXmlExample.xml6. What the Official Docs Won't Tell You
MigrationChecklist.javaTypedQuery query = entityManager.createQuery(7. Migration Checklist

Key takeaways

1
Hibernate 6 is a major upgrade with breaking changes
Jakarta namespace, new type system, SQM query engine, and different default identifier generators.
2
Always test with a real database using Testcontainers. H2 in-memory database can mask Hibernate-specific issues.
3
Follow the migration checklist
update imports, XML files, custom types, queries, and identifier generators. Gradual rollout with monitoring is critical.
4
Enable hibernate.query.sqm_problems_are_thrown=true and hibernate.id.new_generator_mappings=false (temporary) during migration to surface issues.
5
Bytecode enhancement is now the default for lazy loading. Add the Hibernate enhancement plugin to your build to avoid LazyInitializationException.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the most significant breaking change in Hibernate 6 for custom t...
Q02SENIOR
Explain the change in default sequence optimizer from Hibernate 5 to 6 a...
Q03SENIOR
What is SQM and how does it affect HQL queries?
Q01 of 03SENIOR

What is the most significant breaking change in Hibernate 6 for custom type users?

ANSWER
The type system was overhauled. The old org.hibernate.type.Type interface is deprecated. Custom UserType implementations must now implement org.hibernate.usertype.UserType<T> with generics, and the nullSafeGet/nullSafeSet methods have different signatures (using int position instead of String[] names).
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can I use Hibernate 6 with Spring Boot 2.x?
02
What happens to my `javax.persistence` annotations if I don't migrate?
03
Is Hibernate 6 backward compatible with Hibernate 5?
04
How do I handle the `hibernate.id.new_generator_mappings` property?
05
My HQL query worked in Hibernate 5 but fails in 6. What should I do?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Hibernate & JPA. Mark it forged?

7 min read · try the examples if you haven't

Previous
Hibernate Relationship Mappings Deep Dive: @OneToOne, @OneToMany, @ManyToMany Best Practices
25 / 28 · Hibernate & JPA
Next
Spring Data JPA Projections: DTOs, Interfaces, and Dynamic Projections