Java 21 ยท JPA
Spring Boot 3 ยท Java 21 ยท Verified Fix

Java Records & JPA: The Compatibility Guide

Why Java Records cannot be used as @Entity classes โ€” and how to leverage them as first-class DTOs and projections in Hibernate 6.

Framework
Spring Boot 3.x
Runtime
Java 21 LTS
Stability
Enterprise Grade

Technical Briefing

Java Records are a compelling choice for data-carrying types: concise, immutable, and expressive. But they are fundamentally incompatible with JPA's @Entity requirement. Hibernate needs to subclass your entity to create lazy-loading proxies, and it needs a no-args constructor to instantiate entities via reflection. Records are final classes with no no-args constructor โ€” both requirements are violated. The good news: Records excel as DTOs and projections in Hibernate 6, where they are directly supported.

โš  Signal Detected

exception_report.log
FATAL
// Attempting to annotate a Record as a JPA Entity
class="hi-ann">@Entity
public record User(class="hi-ann">@Id Long id, String name, String email) {}

// Runtime exception on startup:
org.hibernate.MappingException: 
  Could not instantiate tuplizer 
  [org.hibernate.tuple.entity.PojoEntityTuplizer] 
  for entity [com.example.entity.User]
  
Caused by: java.lang.NoSuchMethodException: 
  com.example.entity.User.<init>()  
  // Hibernate requires a no-args constructor โ€” Records don't have one

โ—Ž Trace Analysis

JPA mandates that entity classes be non-final (Hibernate must subclass them to generate proxies for lazy loading), have a no-args constructor (Hibernate uses reflection to instantiate them), and support mutable state (Hibernate's dirty-checking mechanism modifies field values). All three requirements are directly violated by Java Records.

โœฆ Remediation Plan

  1. Keep @Entity classes as standard POJOs (or use Lombok @Data/@Builder to reduce boilerplate).

  2. Use Records as DTOs in your API layer โ€” they are perfect for immutable request/response bodies.

  3. Use Records as constructor expression targets in JPQL: SELECT new com.example.dto.UserDto(u.id, u.name) FROM User u.

  4. Use Records as Spring Data interface projection types โ€” Hibernate 6 maps them automatically.

  5. Use @Embeddable Records for value objects (Address, Money) in Hibernate 6.2+ โ€” this is officially supported.

Production Implementation
SafeJava 21
// โŒ ILLEGAL โ€” Record cannot be a JPA Entity
class="hi-ann">@Entity
public record UserEntity(class="hi-ann">@Id Long id, String email) {}

// โœ… CORRECT โ€” Standard POJO Entity (with Lombok)
class="hi-ann">@Entity
class="hi-ann">@Table(name = "users")
class="hi-ann">@Getter class="hi-ann">@Setter class="hi-ann">@NoArgsConstructor
public class User {
    class="hi-ann">@Id class="hi-ann">@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}

// โœ… RECORD AS DTO (API response โ€” perfect use case)
public record UserDto(Long id, String name) {}

// โœ… RECORD IN JPQL CONSTRUCTOR EXPRESSION (Hibernate 6+)
class="hi-ann">@Query("""
    SELECT new com.example.dto.UserDto(u.id, u.name)
    FROM User u WHERE u.active = true
""")
List<UserDto> findActiveUsers();

// โœ… RECORD AS @Embeddable (Hibernate 6.2+ supported!)
class="hi-ann">@Embeddable
public record Address(String street, String city, String postcode) {}

class="hi-ann">@Entity
public class User {
    class="hi-ann">@Id private Long id;
    class="hi-ann">@Embedded private Address address; // Record embedded directly!
}

โŸ Engineering Deep-Dive

Why Hibernate Needs Subclassing

When you call entityManager.getReference(User.class, id) or access a lazy class="hi-ann">@ManyToOne, Hibernate returns a proxy โ€” a CGLIB-generated subclass of your entity. This proxy contains only the ID; it fetches the full data on first property access. Since record is a final class, CGLIB cannot extend it, making lazy loading impossible.

Hibernate 6 Record Support

Hibernate 6.2 (shipped with Spring Boot 3.1+) added official support for Records as class="hi-ann">@Embeddable types. This is ideal for Domain-Driven Design value objects. The Record must be class="hi-ann">@Embeddable, not class="hi-ann">@Entity, and Hibernate instantiates it via its canonical constructor rather than reflection.

Spring Data Projections with Records

Spring Data JPA supports class-based projections (closed projections) via Records when you use constructor expressions. Alternatively, interface projections are proxy-based and work dynamically โ€” but Records as constructor targets give you type-safe, IDE-refactorable projections with zero runtime overhead.

โ—‡ Elite Standards

  1. Engineering Rule

    Use MapStruct to generate compile-time safe Entity-to-Record mappers โ€” avoid manual field-by-field copying in service methods.

  2. Engineering Rule

    Define all API request/response types as Records โ€” they enforce immutability and eliminate defensive copying.

  3. Engineering Rule

    Use Records as constructor projection targets in JPQL instead of full entity loads for any read-only endpoint.

FAQ

What causes Java Records & JPA: The Compatibility Guide in Spring Boot 3?
JPA mandates that entity classes be non-final (Hibernate must subclass them to generate proxies for lazy loading), have a no-args constructor (Hibernate uses reflection to instantiate them), and support mutable state (Hibernate's dirty-checking mechanism modifies field values). All three requirements are directly violated by Java Records.
How do I fix Java Records & JPA: The Compatibility Guide?
Keep @Entity classes as standard POJOs (or use Lombok @Data/@Builder to reduce boilerplate). Use Records as DTOs in your API layer โ€” they are perfect for immutable request/response bodies. Use Records as constructor expression targets in JPQL: SELECT new com.example.dto.UserDto(u.id, u.name) FROM User u. Use Records as Spring Data interface projection types โ€” Hibernate 6 maps them automatically. Use @Embeddable Records for value objects (Address, Money) in Hibernate 6.2+ โ€” this is officially supported.
Best practice #1 for preventing Java 21 ยท JPA errors?
Use MapStruct to generate compile-time safe Entity-to-Record mappers โ€” avoid manual field-by-field copying in service methods.
Best practice #2 for preventing Java 21 ยท JPA errors?
Define all API request/response types as Records โ€” they enforce immutability and eliminate defensive copying.
Best practice #3 for preventing Java 21 ยท JPA errors?
Use Records as constructor projection targets in JPQL instead of full entity loads for any read-only endpoint.

Feedback

Live
ML

M. Leachouri

Founder & Chief Architect

"I built Kodivio because professional tools shouldn't come at the cost of your privacy. Our mission is to provide enterprise-grade utilities that process data exclusively in your browser."

M. Leachouri is an Expert Web Developer, Data Scientist Engineer, and Systems Architect with a deep specialization in DevOps and Cybersecurity. With over a decade of experience building scalable distributed systems and Zero-Trust architectures, he engineered Kodivio to bridge the gap between high-performance computing and absolute user sovereignty.

Verified Expert
Certified Architect
Full Profile & Mission โ†’