java-records-clases-datos-inmutables

Records in Java: concise data classes

  • 4 min

A record is a class designed to represent data through a fixed set of components.

Before records, a simple data class required a lot of boilerplate code.

You want to create a simple class to store a Product (name and price). Nothing else. Just data.

To do it “properly” in classic Java, you had to write:

  1. Private attributes.
  2. The constructor.
  3. Getters (and maybe setters).
  4. The equals() method (for comparisons).
  5. The hashCode() method (for use in maps).
  6. The toString() method (so printing doesn’t show garbage).

Result: 50 lines of code for something that should take 1. It’s boring, clutters the project, and is error-prone.

No more! With the arrival of Records, Java solves this from the ground up.

What is a record?

A record is a special type of class designed specifically to be an immutable data carrier.

The premise is: “If you tell me what data you want, I (the compiler) will generate all the boring code for you.”

The comparison: class vs record

Look at this. It’s the same thing, written in two ways.

Classic approach (POJO):

public class Product {
    private final String name;
    private final double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }

    @Override
    public String toString() { return "Product[name=" + name + ", price=" + price + "]"; }

    // ... Imagine 20 more lines of equals() and hashCode() here ...
}
Copied!

Modern approach (Record):

public record Product(String name, double price) { }
Copied!

Yes. A single line. That’s all.

What happens under the hood?

When you compile that record, Java automatically generates for you:

  1. Canonical constructor: Receives all components (name, price).
  2. Private and final attributes: The data is immutable. They cannot be changed once the object is created.
  3. Accessor methods: Note! They aren’t called getName(), they are named the same as the attribute: name() and price().
  4. equals() and hashCode(): Perfectly implemented for value-based comparison (two products with the same name and price will be equal).
  5. toString(): A nice implementation that prints the values.
public static void main(String[] args) {
    Product p1 = new Product("Laptop", 999.99);
    Product p2 = new Product("Laptop", 999.99);

    // Data access (without 'get')
    System.out.println(p1.name());

    // Content comparison (thanks to the generated equals)
    System.out.println(p1.equals(p2)); // true

    // Print
    System.out.println(p1); // Output: Product[name=Laptop, price=999.99]
}
Copied!

Immutability: The key restriction

Records are designed to be Immutable.

  • They have no setters.
  • Their fields are final.

If you want to change a product’s price, you can’t. You have to create a new record with the new price (usually by copying the data from the previous one).

This, which seems like a limitation, is a blessing for concurrent programming and avoiding state bugs. Records are ideal for DTOs (Data Transfer Objects), API responses, or Map keys.

Customizing a record

Although Java generates everything, sometimes we need to add extra logic.

Validation (compact constructor)

Imagine we want to prevent the price from being negative. Records have a special syntax called the Compact Constructor. You don’t need to repeat the parameters or the assignments (this.x = x). You just write the validation logic.

public record Product(String name, double price) {

    // Compact Constructor: Runs BEFORE assigning the values
    public Product {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        if (name == null || name.isBlank()) {
            name = "No Name"; // We can even "normalize" data
        }
    }
}
Copied!

Extra methods

You can add your own methods if needed.

public record Product(String name, double price) {
    public double priceWithVAT() {
        return this.price * 1.21;
    }
}
Copied!