With every release, Java introduces new features that enhance the language’s capability and make it more developer-friendly. One of the most notable additions in Java 14 (and made stable in Java 16) is the Record class. The Record class simplifies the creation of immutable data objects, making code more concise and readable.

What is a Record class?

A Record in Java is a special kind of class that is used to model immutable data. Unlike regular classes, Records are designed to be concise and automatically handle common methods like equals(), hashCode(), and toString().

You can define a Record as follows:

public record Point(int x, int y) {}

This single line of code does a lot:

Key Features of Records

Immutability

Records are inherently immutable, meaning their fields cannot be changed once an instance is created. This immutability is enforced by the fact that all fields in a Record are implicitly final.

Conciseness

Records reduce boilerplate code. You don’t need to manually write constructors, getters, equals(), hashCode(), or toString() methods. This makes the code cleaner and less error-prone.

Component Accessors

The fields in a Record class are called “components,” and Java automatically generates accessor methods for them. These methods have the same name as the component. For example, in the Point record, you can retrieve the x and y values using x() and y() methods, respectively.

Custom Methods and Constructors

While Records are designed to be simple, you can still add custom methods and constructors if needed. However, the primary constructor (the one that takes all components as parameters) is always generated automatically.

public record Point(int x, int y) {
  public Point {
    if (x < 0 || y < 0) {
      throw new IllegalArgumentException("Coordinates cannot be negative");
    }
  }
}

In this example, a custom constructor is defined to enforce a validation rule.

Limitations of Record Class

Inheritance not supported

Records cannot extend other classes. They implicitly extend java.lang.Record, which is a sealed class. However, Records can implement interfaces.

No-arg constructors

Records do not have a no-argument constructor by default. If you need to create an instance of a Record with default values, you’ll need to define those explicitly in the primary constructor or provide a static factory method.

Immutability Constraints

Since all fields in a Record are final, if you have mutable fields (like an array or a list), the immutability guarantee only applies to the reference, not the object it points to. If you need truly immutable collections, consider using classes from libraries like Guava or using List.of() for unmodifiable lists.

Use Cases for Record Class

Records are best suited for modeling simple data carriers or value objects where the primary concern is the data they encapsulate. Here are some common use cases:

1. DTOs (Data Transfer Objects):

When transferring data between layers of an application or across network boundaries, Records offer a simple way to encapsulate the data.

2. Configuration Settings:

Immutable configuration settings can be modeled effectively with Records, ensuring that configuration values cannot be modified after they are set.

3. Responses from APIs:

When receiving data from an API, especially in RESTful services, Records can be used to represent the structure of the data received.

4. Event Data in Event-Driven Systems:

Events in event-driven architectures can be modeled as Records, ensuring that the event data remains immutable and easy to work with.

Conclusion

By reducing boilerplate and promoting immutability, Records help developers focus on the core logic of their applications rather than the repetitive and error-prone task of writing getter methods, constructors, and toString() implementations. Whether you’re modeling data transfer objects, configuration settings, or simple value objects, Records provide a powerful and concise way to handle data in Java.