Hibernate 5 to 6 Upgrade Skill
Overview
Spring Boot 3 uses Hibernate 6, which includes significant changes from Hibernate 5. This skill covers the key migration considerations.
Key Changes
1. Package Namespace
Hibernate 6 uses Jakarta Persistence:
// Before (Hibernate 5)
import javax.persistence.*;
import org.hibernate.annotations.*;
// After (Hibernate 6)
import jakarta.persistence.*;
import org.hibernate.annotations.*;
2. ID Generation Strategies
Hibernate 6 changed the default ID generation strategy:
// Recommended approach for cross-database compatibility
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Or using sequences (preferred for some databases)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
private Long id;
3. Dialect Configuration
Hibernate 6 can auto-detect dialects in most cases:
# Before (often required in Hibernate 5)
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# After (Hibernate 6) - often can be removed
# Hibernate auto-detects based on JDBC URL
# Only specify if you need specific behavior
If you must specify:
# Hibernate 6 dialects (some renamed)
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
4. Query Changes
JPQL Changes
Some JPQL syntax has stricter validation:
// Be explicit about entity aliases
@Query("SELECT u FROM User u WHERE u.active = true")
List<User> findActiveUsers();
// Avoid implicit joins - be explicit
@Query("SELECT u FROM User u JOIN u.roles r WHERE r.name = :roleName")
List<User> findByRoleName(@Param("roleName") String roleName);
Native Queries
Native query result mapping changed:
// Before (Hibernate 5)
@Query(value = "SELECT * FROM users WHERE active = 1", nativeQuery = true)
List<User> findActiveUsersNative();
// After (Hibernate 6) - works the same, but be careful with projections
@Query(value = "SELECT * FROM users WHERE active = true", nativeQuery = true)
List<User> findActiveUsersNative();
5. Type Mappings
Some type mappings changed:
// Enum mapping - explicit is better
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
// Date/Time - use java.time classes
@Column(name = "created_at")
private LocalDateTime createdAt; // Preferred over java.util.Date
6. Fetch Strategies
Default fetch behavior remains similar, but be explicit:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
Deprecation Removals
Removed in Hibernate 6
@Typeannotation - replaced with@JdbcTypeCodeor custom type definitions- Legacy ID generators - use standard JPA generation
@TypeDef- removed, use@Typewith explicit class
Example Migration
// Before (Hibernate 5 with custom type)
@TypeDef(name = "json", typeClass = JsonType.class)
@Type(type = "json")
private JsonNode metadata;
// After (Hibernate 6)
@JdbcTypeCode(SqlTypes.JSON)
private JsonNode metadata;
Configuration Properties
Some Hibernate properties changed:
# Common properties that remain the same
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# Properties that may need review
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
# New in Hibernate 6
spring.jpa.properties.hibernate.timezone.default_storage=NORMALIZE
Testing Considerations
- Entity validation - Hibernate 6 is stricter about entity mappings
- Query parsing - Some queries that worked in H5 may fail in H6
- Performance - Test performance as query execution plans may differ
Troubleshooting
Common Errors
- "Unknown entity" - Ensure entity scanning is configured correctly
- "Could not determine type" - Check type mappings and annotations
- "Query syntax error" - Review JPQL for stricter H6 parsing rules
Critical Breaking Changes
1. HQL/JPQL Query Parsing is Stricter
// BEFORE (Hibernate 5) - this worked but was non-standard
@Query("update from User u set u.active = false where u.id = :id")
// AFTER (Hibernate 6) - remove the optional "from" keyword
@Query("update User u set u.active = false where u.id = :id")
2. Distinct is No Longer Needed for Collections
// BEFORE (Hibernate 5) - needed distinct to avoid duplicates
@Query("select distinct u from User u join fetch u.roles")
// AFTER (Hibernate 6) - duplicates are automatically filtered
@Query("select u from User u join fetch u.roles")
3. Legacy Criteria API Removed
The deprecated org.hibernate.Criteria API is completely removed. You must use JPA Criteria API:
// BEFORE (Hibernate 5 - old Criteria)
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("active", true));
List<User> users = criteria.list();
// AFTER (Hibernate 6 - JPA Criteria)
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.where(cb.equal(root.get("active"), true));
List<User> users = entityManager.createQuery(cq).getResultList();
4. N+1 Query Behavior Changes
Hibernate 6 may generate different SQL for lazy-loaded collections. Monitor your queries:
// Add this to application.properties to monitor queries
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
logging.level.org.hibernate.SQL=debug
Migration Commands
Check for Hibernate 5 Patterns
# Check for old Criteria API usage
grep -r "session.createCriteria\|org.hibernate.Criteria" --include="*.java" .
# Check for deprecated @Type usage
grep -r "@Type(type\s*=" --include="*.java" .
# Check for @TypeDef
grep -r "@TypeDef" --include="*.java" .
# Check for "update from" pattern in queries
grep -r "update from" --include="*.java" .
Fix Common Issues
# Remove "from" keyword in update queries
find . -name "*.java" -type f -exec sed -i 's/update from /update /g' {} +
Performance Monitoring
After migration, monitor for performance regressions:
# Enable Hibernate statistics
spring.jpa.properties.hibernate.generate_statistics=true
# Log slow queries (Hibernate 6.2+)
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=100