Master Core Java Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Module 9

Record Classes in Java

Learn Java records for creating concise immutable data-carrier classes.

📦

1. What is a Record?

A record is a special kind of Java class designed primarily to model immutable data.

Records reduce the amount of boilerplate code required for classes that mainly store data.

Key idea: A record automatically provides useful members such as a constructor, accessors, equals(), hashCode(), and toString().

2. Traditional Data Class

A normal data class can require many lines of repetitive code.

Java
public final class Person {

    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() {
        return name;
    }

    public int age() {
        return age;
    }
}

3. Basic Record Syntax

The same basic data model can be represented much more concisely with a record.

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

The components name and age become part of the record's state.

4. Creating a Record Object

Java
Person person =
    new Person("Rohit", 25);

System.out.println(person.name());
System.out.println(person.age());
Notice: Record accessors use the component name directly, such as person.name(), rather than a traditional getName() method.

5. Automatically Generated Constructor

A record automatically receives a canonical constructor corresponding to its declared components.

Java
public record Employee(
    String name,
    int id
) {
}

Java effectively provides a constructor equivalent to:

Java
new Employee(
    "Samiksha",
    101
);

6. Record Accessor Methods

Each record component automatically gets an accessor method with the same name as the component.

Java
record Product(
    String name,
    double price
) {
}

Product product =
    new Product("Laptop", 55000.0);

System.out.println(product.name());
System.out.println(product.price());

7. toString()

Records automatically provide a useful toString() implementation containing the record name and component values.

Java
record Student(
    String name,
    int marks
) {
}

Student student =
    new Student("Sejl", 85);

System.out.println(student);
Example Output
Student[name=Sejl, marks=85]

8. equals() and hashCode()

Records automatically implement value-based equals() and hashCode() behavior using their components.

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

Point first =
    new Point(10, 20);

Point second =
    new Point(10, 20);

System.out.println(
    first.equals(second)
);
The two records are considered equal because their component values are equal.

9. Records are Shallowly Immutable

Record components are final, so their references or primitive values cannot be reassigned after construction.

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

Person person =
    new Person("Samadhan", 22);

// person.age = 20;
// Invalid because record components cannot be reassigned.
Important: Records provide shallow immutability. If a component refers to a mutable object, that referenced object can still be changed unless it is protected separately.

10. Compact Constructor

A record can define a compact constructor to validate or normalize its component values.

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

    public Person {

        if (age < 0) {
            throw new IllegalArgumentException(
                "Age cannot be negative"
            );
        }
    }
}

In a compact constructor, Java handles the assignment of constructor parameters to record components automatically.

11. Canonical Constructor

You can also explicitly declare the canonical constructor when you need complete control over its implementation.

Java
public record Employee(
    String name,
    int salary
) {

    public Employee(
        String name,
        int salary
    ) {

        if (salary < 0) {
            throw new IllegalArgumentException(
                "Salary cannot be negative"
            );
        }

        this.name = name;
        this.salary = salary;
    }
}

12. Custom Methods in Records

Records can contain custom instance methods just like normal classes.

Java
public record Rectangle(
    double width,
    double height
) {

    public double area() {
        return width * height;
    }
}

Rectangle rectangle =
    new Rectangle(10, 5);

System.out.println(
    rectangle.area()
);

13. Static Members

Records can also contain static fields and static methods.

Java
public record User(
    String username
) {

    public static String type() {
        return "Application User";
    }
}

System.out.println(
    User.type()
);

14. Record Restrictions

Records have several restrictions because they are specifically designed for data-centric modeling.

  • A record cannot extend another class.
  • A record is implicitly final.
  • Record components cannot be reassigned.
  • Instance fields cannot be declared independently of record components.
  • Records can implement interfaces.

15. Records Implementing Interfaces

A record can implement one or more interfaces.

Java
interface Printable {

    void print();
}

record Employee(
    String name,
    int id
) implements Printable {

    public void print() {
        System.out.println(
            name + " - " + id
        );
    }
}

Employee employee =
    new Employee("Sejl", 101);

employee.print();

16. Record vs Class

Record Normal Class
Designed for data carriers General-purpose object modeling
Compact syntax More boilerplate may be required
Components are final Fields can be mutable or immutable
Automatically provides accessors Accessors must usually be written manually
Automatically provides value-oriented equals/hashCode Must be implemented when required
Implicitly final Can be designed for inheritance

17. Records and Inheritance

Records cannot extend user-defined classes because every record already extends java.lang.Record.

Java
public record Person(
    String name
) {
}

// A record cannot extend another class.
// public record Student(...) extends Person { }

However, records can implement interfaces.

18. Generic Records

Records can be generic, allowing them to represent different types of data.

Java
public record Box<T>(
    T value
) {
}

Box<String> text =
    new Box<>("Java");

Box<Integer> number =
    new Box<>(100);

System.out.println(text.value());
System.out.println(number.value());

19. Nested Records

Records can be declared inside other classes when they belong naturally to a larger domain model.

Java
public class OrderService {

    record Order(
        int id,
        double amount
    ) {
    }

    public static void main(String[] args) {

        Order order =
            new Order(101, 2500.0);

        System.out.println(order);
    }
}

20. Records as DTOs

Records are particularly useful for simple Data Transfer Objects, API request objects, response objects, and projection models.

Java
public record UserResponse(
    Long id,
    String name,
    String email
) {
}

A controller or service can return this record as a response model without creating repetitive getter and setter code.

21. Validation in Records

Compact constructors are useful for enforcing invariants.

Java
public record Account(
    String username,
    String email
) {

    public Account {

        if (username == null ||
            username.isBlank()) {

            throw new IllegalArgumentException(
                "Username is required"
            );
        }

        if (email == null ||
            email.isBlank()) {

            throw new IllegalArgumentException(
                "Email is required"
            );
        }
    }
}

22. Example 🌍🫠

Consider a payment application that needs to transfer payment information between layers.

Java
public record PaymentRequest(
    String customerId,
    double amount,
    String currency
) {

    public PaymentRequest {

        if (amount <= 0) {

            throw new IllegalArgumentException(
                "Amount must be greater than zero"
            );
        }
    }
}
Java
PaymentRequest request =
    new PaymentRequest(
        "CUST-101",
        2500.0,
        "INR"
    );

System.out.println(
    request.customerId()
);

System.out.println(
    request.amount()
);

System.out.println(
    request.currency()
);

23. Records and Serialization

Records can be used with many Java libraries that work with Java objects, including JSON serialization libraries, provided the library supports records.

Java
public record ProductResponse(
    Long id,
    String name,
    double price
) {
}

This makes records convenient for API response models in modern Java applications.

24. Java Version

Java Version Record Status
Java 14 First preview
Java 15 Second preview
Java 16 Standard feature

25. Advantages of Records

  • Very concise syntax.
  • Less boilerplate code.
  • Built-in accessors.
  • Built-in equals and hashCode.
  • Useful toString implementation.
  • Good fit for immutable data carriers.
  • Excellent for DTOs and API models.
  • Can implement interfaces.
  • Can contain validation and business-related helper methods.

26. Limitations of Records

  • Cannot extend another class.
  • Are implicitly final.
  • Not suitable for highly mutable domain objects.
  • Provide shallow rather than deep immutability.
  • Should not be used merely because they are shorter.

27. Best Practices

  • Use records for data-centric models.
  • Use compact constructors for validation.
  • Keep record methods focused and simple.
  • Use normal classes when the object requires mutable state or inheritance.
  • Use records for DTOs when the application architecture benefits from immutable data.
  • Remember that referenced mutable objects are not automatically deeply immutable.

28. Interview Questions

A record is a special Java class designed primarily for representing immutable data with less boilerplate.

Records became a standard language feature in Java 16.

Record components are final, so their references or primitive values cannot be reassigned. However, records provide only shallow immutability.

No. A record cannot extend a user-defined class. It implicitly extends java.lang.Record.

Yes. Records can implement one or more interfaces.

A compact constructor is a concise constructor form used mainly for validation and normalization of record components.

Summary

In this lesson, you learned:

  • What Java records are
  • Record syntax
  • Automatic constructors and accessors
  • equals(), hashCode(), and toString()
  • Shallow immutability
  • Compact constructors
  • Custom methods and static members
  • Records implementing interfaces
  • Generic records
  • Records as DTOs
  • Validation inside records
  • Advantages and limitations