In Java, it’s common to have classes that exist solely to carry data, i.e., classes with fields, simple methods like getters, equals()
, hashCode()
, and toString()
. Before records, defining such classes could be quite verbose.
A record is a restricted form of class that provides a concise way to define such classes. When you define a record, the Java compiler automatically implements several standard methods for you.
final
and their fields are also final
. Thus, once a record is created, you cannot modify its state.equals()
, hashCode()
, and toString()
based on the fields of the record.public record Point(int x, int y) { }
This simple declaration creates:
x
and y
.x()
and y()
).equals()
method that checks for equality based on the values of x
and y
.hashCode()
method based on the values of x
and y
.toString()
method that returns a string in the format Point[x=<value>, y=<value>]
.public class TestRecords { public static void main(String[] args) { Point p1 = new Point(5, 10); Point p2 = new Point(5, 10); System.out.println(p1); // Prints: Point[x=5, y=10] System.out.println(p1.equals(p2)); // Prints: true } }
While records are meant to be simple data carriers, you can still customize them:
You can define custom constructors, but they should call the canonical constructor:
public record Rectangle(int width, int height) { public Rectangle { if (width < 0 || height < 0) { throw new IllegalArgumentException("Dimensions should be positive"); } } }
You can add methods to a record:
public record Point(int x, int y) { public double distanceFromOrigin() { return Math.sqrt(x * x + y * y); } }
Records can implement interfaces:
public record NamedPoint(int x, int y, String name) implements Comparable<NamedPoint> { @Override public int compareTo(NamedPoint other) { return name.compareTo(other.name); } }
Things to Remember:
Record components are final by default.
Records can’t be abstract.
Records can’t extend other classes.
You can’t declare instance fields in a record that aren’t record components.
Records are a significant step toward making Java more expressive and less verbose, especially for common coding patterns.
Caveats and Considerations for using Record with JPA:
Annotations: With records, you can’t annotate fields directly since they’re declared in the record header. If you need to add JPA annotations (like @Id, @GeneratedValue, etc.), they should be added to the accessor methods (getters) of the record.
Immutability: Records are inherently immutable, which means all their fields are final. This behavior aligns with the principle of immutability in entity design but can complicate scenarios where you want to change an entity’s state after it has been constructed.
No–Args Constructor: JPA typically requires a no–args constructor, which records don’t provide out of the box. Hibernate’s (a popular JPA implementation) team has been working to support records, so expect improvements in this area. If your JPA provider doesn’t support records yet, consider using DTOs (Data Transfer Objects) alongside your entities.
Experimental Support: As mentioned earlier, while Spring Data started introducing experimental support for records, this might not be fully mature or cover all scenarios. Always refer to the latest Spring Data documentation and check for compatibility and best practices.