Java Records: A New Way to Handle Immutable Data

P

Pradeep Rai

7 min read

πŸ“Œ What is a Record?

Introduced in Java 14 (preview) and made stable in Java 16.

A record is a special kind of class designed to hold immutable data.

It automatically provides:

  • equals()
  • hashCode()
  • toString()
  • A constructor and getters for all fields.

πŸ‘‰ In short: A record is a concise way to create data carrier classes.


βœ… When to Use Records

  • When you need a data model class.
  • For DTOs (Data Transfer Objects) in APIs.
  • For database results mapping.
  • When you want immutability and simplicity.

Example

1public record Person(String name, int age) {}

This is equivalent to a full POJO with private fields, constructor, getters, equals, hashCode, and toString.


❌ When NOT to Use Records

  • When you need mutable fields.
  • When your class has complex business logic.
  • If inheritance is required β†’ records cannot extend other classes (but they can implement interfaces).
  • If you want to hide fields or not expose them as getters.

πŸ”‘ Equals and HashCode in Records

Automatically generated based on all components.
Equality is value-based, not reference-based.

Example

1record Point(int x, int y) {} 2 3Point p1 = new Point(1, 2); 4Point p2 = new Point(1, 2); 5 6System.out.println(p1.equals(p2)); // true 7System.out.println(p1.hashCode() == p2.hashCode()); // true

πŸ—οΈ Canonical Constructor

The canonical constructor is the one that takes all record fields as parameters.

1public record User(String username, String email) { 2 // Canonical constructor 3 public User { 4 if (username == null || username.isBlank()) { 5 throw new IllegalArgumentException("Username cannot be empty"); 6 } 7 } 8}

Here, Java automatically maps parameters β†’ fields. You can add validations but don’t need to write assignments manually.


πŸ“š Examples

Simple Record

1record Book(String title, String author, double price) {}

Record with Validation

1record Employee(String id, String name, double salary) { 2 public Employee { 3 if (salary < 0) { 4 throw new IllegalArgumentException("Salary must be positive"); 5 } 6 } 7}

Record Implementing Interface

1interface Identifiable { 2 String id(); 3} 4 5record Student(String id, String name) implements Identifiable {}

❓ Trick Questions

Can records extend classes?
➝ No. They implicitly extend java.lang.Record.

Are records mutable?
➝ No. All fields are final.

Can records have static fields or methods?
➝ Yes, just like normal classes.

Can you add methods to a record?
➝ Yes, you can define additional methods.

Are records serializable?
➝ Yes, if all components are serializable.

Can you override equals and hashCode?
➝ Yes, but not recommended unless you have a strong reason.


πŸš€ Conclusion

  • Use records for simple, immutable data carriers.
  • Avoid them for complex, mutable, or inheritance-heavy scenarios.
  • They help write cleaner and more concise code with less boilerplate.

Comments