Records

Intermediate
Modern Java (9–21)

Declare immutable data carriers in one line — records auto-generate constructor, getters, equals, hashCode, and toString.

Overview

Records (Java 16, preview in 14/15) are a special class form for transparent data carriers. A record declaration like record Point(int x, int y) {} automatically generates: a canonical constructor, accessor methods x() and y(), equals(), hashCode(), and toString(). Records are implicitly final and all components are final. They are the perfect replacement for verbose POJO/DTO classes.

Declaring & Using Records

Record syntax: record Name(ComponentList) { optional body }. Components become private final fields with public accessor methods named after the component (not getX() — just x()). Records can implement interfaces, have static members, and have additional methods.

RecordDemo.java
import java.util.*;

public class RecordDemo {
    // Basic record — all boilerplate generated automatically
    record Point(double x, double y) {
        // Can add instance methods
        public double distanceTo(Point other) {
            double dx = this.x - other.x;
            double dy = this.y - other.y;
            return Math.sqrt(dx*dx + dy*dy);
        }

        // Static factory method
        public static Point origin() { return new Point(0, 0); }
    }

    // Record implementing interface
    interface Shape { double area(); }
    record Circle(double radius) implements Shape {
        // Compact constructor — validation only, no need to assign fields
        Circle {
            if (radius <= 0) throw new IllegalArgumentException("radius must be positive");
        }
        @Override public double area() { return Math.PI * radius * radius; }
    }

    public static void main(String[] args) {
        var p1 = new Point(3, 4);
        var p2 = Point.origin();

        // Auto-generated accessors: x(), y() (not getX)
        System.out.println(p1.x());   // 3.0
        System.out.println(p1.y());   // 4.0

        // Auto-generated toString
        System.out.println(p1);       // Point[x=3.0, y=4.0]

        // Auto-generated equals + hashCode
        var p3 = new Point(3, 4);
        System.out.println(p1.equals(p3)); // true
        System.out.println(p1 == p3);      // false

        System.out.println(p1.distanceTo(p2)); // 5.0

        var c = new Circle(5);
        System.out.printf("Area: %.2f%n", c.area()); // 78.54

        try { new Circle(-1); } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage()); // radius must be positive
        }

        // Records work perfectly in collections
        Set<Point> points = Set.of(p1, p2, new Point(1,1));
        System.out.println(points.contains(new Point(3,4))); // true
    }
}

Canonical & Compact Constructors

The canonical constructor has the same signature as the component list. You can override it to add validation or transformation. The compact constructor omits the parameter list — the compiler provides the parameters and auto-assigns fields after the body runs. Use compact constructors for validation and normalisation.

ConstructorRecords.java
import java.util.Objects;

public class ConstructorRecords {
    // Custom canonical constructor
    record Range(int min, int max) {
        Range(int min, int max) {
            if (min > max) throw new IllegalArgumentException("min > max");
            this.min = min;
            this.max = max;
        }
        public boolean contains(int value) { return value >= min && value <= max; }
    }

    // Compact constructor — normalise + validate
    record PersonName(String first, String last) {
        PersonName {
            Objects.requireNonNull(first, "first name required");
            Objects.requireNonNull(last,  "last name required");
            first = first.trim().toLowerCase(); // normalise
            last  = last.trim().toLowerCase();
            // compiler assigns this.first = first, this.last = last automatically
        }
        public String fullName() {
            return Character.toUpperCase(first.charAt(0)) + first.substring(1)
                 + " "
                 + Character.toUpperCase(last.charAt(0)) + last.substring(1);
        }
    }

    public static void main(String[] args) {
        var range = new Range(1, 10);
        System.out.println(range.contains(5));  // true
        System.out.println(range.contains(15)); // false

        var name = new PersonName("  ALICE  ", "  Smith  ");
        System.out.println(name.first()); // alice
        System.out.println(name.fullName()); // Alice Smith
    }
}

Records vs Classes & Limitations

Records cannot: extend another class (they implicitly extend Record), declare instance fields beyond components, be abstract, have mutable components (all are final). They can: implement interfaces, have static fields/methods, add instance methods, and be nested.

Ideal for: DTOs, value objects, API responses, Map keys (equals/hashCode auto-generated correctly).

RecordPractice.java
import java.util.*;
import java.util.stream.*;

public class RecordPractice {
    record Product(String id, String name, double price) {}
    record PageResult<T>(List<T> items, int page, int totalPages) {}

    public static void main(String[] args) {
        var products = List.of(
            new Product("p1", "Laptop", 999.0),
            new Product("p2", "Phone",  699.0),
            new Product("p3", "Tablet", 499.0)
        );

        // Records as Map keys — equals/hashCode just work
        Map<Product, Integer> stock = new HashMap<>();
        stock.put(products.get(0), 10);
        stock.put(products.get(1), 25);
        System.out.println(stock.get(new Product("p1","Laptop",999.0))); // 10

        // Stream pipeline with records
        var page = new PageResult<>(
            products.stream()
                    .filter(p -> p.price() < 800)
                    .collect(Collectors.toList()),
            1, 1);
        System.out.println(page.items().size()); // 2
        System.out.println(page); // PageResult[items=[...], page=1, totalPages=1]

        // "Wither" pattern — create modified copy
        var laptop = products.get(0);
        var discounted = new Product(laptop.id(), laptop.name(), laptop.price() * 0.9);
        System.out.println(discounted.price()); // 899.1
    }
}

Key Points to Remember

  • record auto-generates canonical constructor, accessors (x() not getX()), equals, hashCode, toString
  • Records are implicitly final — cannot be extended; all components are implicitly private final
  • Compact constructor (no param list) auto-assigns fields after body — ideal for validation/normalisation
  • Records can implement interfaces, add instance/static methods, and be generic
  • Records make perfect Map keys and Set elements — equals/hashCode are correct by default
  • Use records for DTOs, value objects, and any class whose purpose is to hold data

Practice Records in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is a record in Java and when was it introduced?

EasyAmazon
2

What methods does a record automatically generate?

EasyGoogle
3

What is a compact constructor in a record?

MediumOracle
4

Can a record extend another class?

EasyTCS
5

What is the difference between a record and a traditional POJO class?

MediumMicrosoft

Ask Aria about Records

Your personal AI tutor — ask anything about this concept