Java Records in Action: A Hands-On Tutorial

Java has consistently evolved to provide developers with more expressive, concise, and efficient ways to write code. One of the major additions in Java 14 was the introduction of records — a new type of class that simplifies the creation of immutable data carriers. This article thoroughly explores Java records, including their syntax, features, use cases, limitations, and best practices.

What is a Java Record?

record in Java is a special class designed to be a simple data carrier. It reduces boilerplate code by automatically generating commonly used methods like equals()hashCode(), and toString().

Java records were introduced as a preview feature in Java 14, finalized in Java 16, and are now a permanent part of the Java language.

Why Were Records Introduced?

Before records, creating a simple data class in Java involved writing a lot of repetitive code:

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Person person)) return false;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

This is a lot of code. Java records simplify this class.

Syntax of Java Record

Using a record, the Person class can be written as:

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

Explanation:

  • Declares a record named Person
  • Defines two fields: name and age
  • Automatically generates:
  • constructor
  • Getters (but no “get” prefix, e.g., name() instead of getName())
  • equals()hashCode(), and toString() methods

Syntax of Lombok Value annotation

Using a Lombok, the Person class can be written as:

import lombok.Value;

@Value
public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
        this.name = name;
        this.age = age;
    }
}

Explanation:

Value annotation automatically:

  • Makes the class final
  • Makes all fields private and final
  • Generates getter methods (named exactly like the fields, just like records)
  • Generates toString()equals(), and hashCode()
  • No setters (immutability by default)

Features of Java Records

1. Immutable by Default

  • All fields in a record are final and cannot be modified after object creation.

2. Concise Syntax

  • Removes boilerplate code like getters, constructors, and toString().

3. Auto-generated Methods

  • Records automatically implement equals()hashCode(), and toString().

4. Can Implement Interfaces

  • A record can implement an interface but cannot extend another class.

5. Compact Constructors

  • Records allow constructors to be customized while still maintaining immutability.

Examples of Java Records

1. Using a Record

@Slf4j
public class RecordExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);
        log.info(person.getName());  // Alice
        log.info(String.valueOf(person.getAge()));   // 30
        log.info(String.valueOf(person));         // Person[name=Alice, age=30]
    }
}

2. Custom Constructor

By default, records provide a constructor matching the defined fields. However, developers can define a custom compact constructor:

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

Here, we add validation to ensure the age is non-negative.

Exception in thread "main" java.lang.IllegalArgumentException: Age cannot be negative
 at com.example.programming.model.Person.<init>(Person.java:11)
 at com.example.programming.utils.example.RecordExample.main(RecordExample.java:12)

3. Implementing an Interface

Records can implement interfaces:

public interface Describable {
    String describe();
}

public record Person(String name, int age) implements Describable {
    @Override
    public String describe() {
        return name + " is " + age + " years old.";
    }
}

Limitations of Java Records

While records are helpful, they come with some restrictions:

1. Cannot Extend Classes

  • A record cannot be inherited from another class. It only extends java.lang.Record implicitly.

2. Immutable Fields

  • Fields in a record cannot be changed after initialization.

3. No Additional Instance Variables

  • All fields must be declared in the record header; extra instance variables are not allowed.

4. Cannot Modify Default Methods

  • Records automatically provide equals()hashCode(), and toString(), and overriding them requires careful implementation.

When to Use Java Records

Java records

Example: Using Java Record in a REST API

Records are great for defining API response models:

@RestController
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        return new User("John Doe", "john@example.com");
    }

    public record User(String name, String email) {}
}

Here, User is a simple DTO with minimal code.

Java Records

Comparison: Records vs Lombok

Many Java developers use Lombok to reduce boilerplate code. Here’s a comparison:

Comparison

If you’re using Java 16 or newer, records are preferable since they are built into the language.

Conclusion

Java records provide a powerful and concise way to create immutable data structures while eliminating repetitive boilerplate code. They are best suited for data-centric classes such as DTOs, API responses, and configuration models. However, they do not replace regular classes, especially if mutability or inheritance is required.

Leave a Comment

Your email address will not be published. Required fields are marked *