DEV Community

Linda lindo
Linda lindo

Posted on

Why I Can't Use Java Records as JPA Entities

With the introduction of Java Records (officially in Java 16), developers finally got a concise way to create immutable data carrier classes without writing boilerplate code like equals(), hashCode(), and toString().

Naturally, many of us looked at our verbose JPA Entities and thought:

"Can I replace these with Records to make my code cleaner?"

The short answer is: No, you cannot.

While Records are perfect for DTOs (Data Transfer Objects), they are fundamentally incompatible with the design patterns used by JPA (Java Persistence API) and Hibernate. Here are the two main technical reasons why.

1. The Need for Mutability

The core philosophy of a Java Record is immutability. When you create a record, all its fields are implicitly final. Once an instance is created, its state cannot be changed.

JPA, however, requires mutability.

JPA operates by managing the state of an entity. When you load an object from the database, modify a field, and commit the transaction, JPA relies on the ability to change the values inside that object instance.

  • Dirty Checking: Hibernate needs to compare the current state of an object with its original snapshot.
  • Setters: JPA needs to set values when reflecting data from the database into the object.

Since Records do not have setters and their fields are final, JPA cannot manage their lifecycle or update their state.

2. Proxy Generation and Lazy Loading

One of the most powerful features of JPA is Lazy Loading. This allows the framework to fetch data only when it is actually accessed, saving memory and database performance.

To implement Lazy Loading, JPA providers (like Hibernate) create Proxies.

  • A proxy is a dynamically generated class that inherits from your original entity class.
  • It overrides the getter methods to trigger a database call when requested.

The problem? Records are final classes.

In Java, a final class cannot be extended (inherited). Because a proxy is essentially a subclass of your entity, it is technically impossible for JPA to create a proxy for a Java Record. Without proxies, critical JPA features like lazy loading break down completely.

The Solution: Standard Classes + Lombok

So, if we can't use Records, are we stuck with verbose code? Not necessarily.

You should continue to use standard class structures for your Entities, but you can achieve similar conciseness using Lombok.

❌ Don't use this (Record)

// This will cause issues with JPA!
// Records are immutable and final, which JPA cannot handle.
public record User(Long id, String name, String email) {}
Enter fullscreen mode Exit fullscreen mode

✅ Do use this (Class + Lombok)

By using Lombok annotations, you can keep your code clean while satisfying JPA requirements (mutability, default constructors, and non-final classes).

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Good practice: Limit access to the default constructor
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String email;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Java Records are a fantastic addition to the language, but they are designed for specific use cases—primarily as immutable data carriers (DTOs).
JPA Entities, on the other hand, represent mutable state mapped to a database and require a lifecycle that Records simply cannot support.

Best Practice:

  • Use Standard Classes (with Lombok) for Entities.
  • Use Records for DTOs.

Top comments (0)