Java Record Constructors

This blog post demonstrates the use of constructors in Java records. Records provide a concise syntax to declare classes that are intended to be simple data carriers.

Java Records are designed to be concise and to express a clear intent: to serve as transparent carriers of immutable data. The constructor in a Java record plays a crucial role in this design. 

1. Implicit Public Constructor 

When you declare a Java record, it automatically generates a public constructor with all the fields as parameters. This constructor is known as the canonical constructor. 

Example of Implicit Constructor

public record User(String name, int age) {}
// Automatically generates: User(String name, int age)

Here, you don’t need to explicitly define the constructor; Java does it for you.

2. Compact Constructor

Java Records also introduced a concept called the compact constructor. This variant allows you to validate and normalize field values without repeating parameter names and types.

Example of Compact Constructor

public record User(String name, int age) {
    public User {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
}

In this example, the compact constructor allows for age validation without the need to redeclare the parameters.

3. Custom Constructor Behavior

While records aim to simplify data representation, they also offer the flexibility to include custom behavior in the constructor.

Example of Custom Constructor Behavior

public record User(String name, int age) {
    public User(String name, int age) {
        this.name = name.trim(); // Custom normalization
        this.age = age;
    }
}

4. Record with a Compact Constructor for Validation - Complete Example with Output

In this example, a Person record is defined with a custom constructor to validate its fields.

public record Person(String name, int age) {
    public Person {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Person person = new Person("Alice", 30);
            System.out.println(person);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }

        try {
            Person invalidPerson = new Person("", -1);
            System.out.println(invalidPerson);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

Output:

Person[name=Alice, age=30]
Name cannot be blank
Age cannot be negative

Explanation:

1. The Person record has two fields: name and age.

2. A custom constructor is defined within the Person record. This constructor adds validation logic:

- It checks if the name is not null or blank and if age is not negative.

3. The main method attempts to create two Person instances.

4. The first instance with valid data is created successfully and printed.

5. The second instance with invalid data triggers the validation logic, and exceptions are caught and printed.

Benefits of Using Java Record Constructors 

Simplicity and Clarity: With an automatic generation of constructors, records reduce boilerplate code and make the codebase cleaner and more readable. 

Data Integrity: The immutability enforced by record constructors ensures that the state of the record remains consistent and reliable throughout its lifecycle. 

Ease of Use: Developers can focus more on the business logic rather than worrying about the nuances of constructor creation. 

Validation and Normalization: Compact constructors in records allow for built-in validation and normalization, which are crucial for maintaining data quality. 

Comments