You need a Point class with x and y. Writing constructor, getters, equals, hashCode, and toString is tedious. Records do all this in one line: record Point(int x, int y) {}. Immutable data carriers with no boilerplate.

Basic record

Define a data class in one line.

BasicRecord.java
// Basic record definition and usage
// Concept: record - immutable data carrier

// Define a record
record Point(int x, int y) {
}

public class BasicRecord {
    public static void main(String[] args) {
        // Create record instance
        int pointX = ;
        Point p1 = new Point(pointX, 20);

        // Access fields via accessor methods
        System.out.println("x: " + p1.x());
        System.out.println("y: " + p1.y());
        
        
        // Automatic toString()
        System.out.println("Point: " + p1);
        
        // Create another instance
        Point p2 = new Point(10, 20);
        Point p3 = new Point(15, 25);
        
        // Automatic equals()
        System.out.println("\np1 equals p2: " + p1.equals(p2));
        System.out.println("p1 equals p3: " + p1.equals(p3));
        
        // Use == for reference comparison
        System.out.println("p1 == p2: " + (p1 == p2));
        
        // More record examples
        record Person(String name, int age) {}
        
        Person alice = new Person("Alice", 30);
        System.out.println("\nPerson: " + alice);
        System.out.println("Name: " + alice.name());
        System.out.println("Age: " + alice.age());
        
    }
}
// Basic record definition and usage
// Concept: record - immutable data carrier

// Define a record
record Point(int x, int y) {
}

public class BasicRecord {
    public static void main(String[] args) {
        // Create record instance
        int pointX = ;
        Point p1 = new Point(pointX, 20);

        // Access fields via accessor methods
        System.out.println("x: " + p1.x());
        System.out.println("y: " + p1.y());
        
        
        // Automatic toString()
        System.out.println("Point: " + p1);
        
        // Create another instance
        Point p2 = new Point(10, 20);
        Point p3 = new Point(15, 25);
        
        // Automatic equals()
        System.out.println("\np1 equals p2: " + p1.equals(p2));
        System.out.println("p1 equals p3: " + p1.equals(p3));
        
        // Use == for reference comparison
        System.out.println("p1 == p2: " + (p1 == p2));
        
        // More record examples
        record Person(String name, int age) {}
        
        Person alice = new Person("Alice", 30);
        System.out.println("\nPerson: " + alice);
        System.out.println("Name: " + alice.name());
        System.out.println("Age: " + alice.age());
        
    }
}
// Basic record definition and usage
// Concept: record - immutable data carrier

// Define a record
record Point(int x, int y) {
}

public class BasicRecord {
    public static void main(String[] args) {
        // Create record instance
        int pointX = ;
        Point p1 = new Point(pointX, 20);

        // Access fields via accessor methods
        System.out.println("x: " + p1.x());
        System.out.println("y: " + p1.y());
        
        
        // Automatic toString()
        System.out.println("Point: " + p1);
        
        // Create another instance
        Point p2 = new Point(10, 20);
        Point p3 = new Point(15, 25);
        
        // Automatic equals()
        System.out.println("\np1 equals p2: " + p1.equals(p2));
        System.out.println("p1 equals p3: " + p1.equals(p3));
        
        // Use == for reference comparison
        System.out.println("p1 == p2: " + (p1 == p2));
        
        // More record examples
        record Person(String name, int age) {}
        
        Person alice = new Person("Alice", 30);
        System.out.println("\nPerson: " + alice);
        System.out.println("Name: " + alice.name());
        System.out.println("Age: " + alice.age());
        
    }
}

record Name(Type field1, Type field2) {} - complete data class.

record Immutable data carrier. Auto-generates constructor, accessors, equals, hashCode, toString.

Automatic methods

What records give you for free.

AutomaticMethods.java
// Automatic methods in records
// Concept: automatic methods - equals, hashCode, toString

record Book(String title, String author, int year) {
}

public class AutomaticMethods {
    public static void main(String[] args) {
        // Create book instances
        Book b1 = new Book("1984", "Orwell", 1949);
        Book b2 = new Book("1984", "Orwell", 1949);
        Book b3 = new Book("Dune", "Herbert", 1965);
        
        // toString() - readable representation
        System.out.println("Book 1: " + b1);
        System.out.println("Book 2: " + b2);
        System.out.println("Book 3: " + b3);
        
        // equals() - value-based equality
        System.out.println("\nb1.equals(b2): " + b1.equals(b2));
        System.out.println("b1.equals(b3): " + b1.equals(b3));
        
        
        // hashCode() - consistent with equals
        System.out.println("\nHash codes:");
        System.out.println("b1: " + b1.hashCode());
        System.out.println("b2: " + b2.hashCode());
        System.out.println("b3: " + b3.hashCode());
        
        System.out.println("\nSame hash? " + (b1.hashCode() == b2.hashCode()));
        
        // Use in collections
        java.util.Set<Book> books = new java.util.HashSet<>();
        books.add(b1);
        books.add(b2);  // Won't add (equals b1)
        books.add(b3);
        
        System.out.println("\nUnique books in set: " + books.size());
        
        // Use as map key
        java.util.Map<Book, Integer> stock = new java.util.HashMap<>();
        stock.put(b1, 10);
        stock.put(b2, 5);  // Updates b1's value (same key)
        stock.put(b3, 7);
        
        System.out.println("\nStock levels:");
        for (var entry : stock.entrySet()) {
            System.out.println("  " + entry.getKey().title() + ": " + entry.getValue());
        }
        
        // Compare different types
        record Movie(String title, String director, int year) {}
        
        Movie m1 = new Movie("1984", "Radford", 1984);
        
        // Different types, even with same values
        System.out.println("\nBook equals Movie: " + b1.equals(m1));
    }
}

Accessor methods: point.x(). Plus equals, hashCode, toString.

Compact constructor

Validate fields in a compact constructor.

CompactConstructor.java
// Compact constructor for validation
// Concept: compact constructor - validate record creation

record Temperature(double celsius) {
    // Compact constructor for validation
    public Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException(
                "Temperature below absolute zero: " + celsius);
        }
    }
    
    // Add computed methods
    public double fahrenheit() {
        return celsius * 9.0 / 5.0 + 32;
    }
    
    public double kelvin() {
        return celsius + 273.15;
    }
}

record Range(int min, int max) {
    // Compact constructor with normalization
    public Range {
        if (min > max) {
            // Swap if needed
            int temp = min;
            min = max;
            max = temp;
        }
    }
    
    public boolean contains(int value) {
        return value >= min && value <= max;
    }
    
    public int size() {
        return max - min + 1;
    }
}

public class CompactConstructor {
    public static void main(String[] args) {
        // Create temperature with validation
        Temperature t1 = ;
        System.out.println("Temperature: " + t1.celsius() + "°C");
        System.out.println("Fahrenheit: " + t1.fahrenheit() + "°F");
        System.out.println("Kelvin: " + t1.kelvin() + "K");
        
        
        // Validation prevents invalid state
        try {
            Temperature invalid = new Temperature(-300);
            System.out.println("Created: " + invalid);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Range with auto-correction
        Range r1 = new Range(1, 10);
        Range r2 = new Range(10, 1);  // Will be swapped
        
        System.out.println("\nr1: " + r1);
        System.out.println("r2: " + r2);
        System.out.println("r1 equals r2: " + r1.equals(r2));
        
        // Use range methods
        Range range = new Range(5, 15);
        
        System.out.println("\nRange: " + range);
        System.out.println("Size: " + range.size());
        System.out.println("Contains 10: " + range.contains(10));
        System.out.println("Contains 20: " + range.contains(20));
        
        // Multiple validations
        record Email(String address) {
            public Email {
                if (address == null || address.isEmpty()) {
                    throw new IllegalArgumentException("Email cannot be empty");
                }
                if (!address.contains("@")) {
                    throw new IllegalArgumentException("Invalid email format");
                }
                // Normalize to lowercase
                address = address.toLowerCase();
            }
        }
        
        Email e1 = new Email("Alice@Example.COM");
        Email e2 = new Email("alice@example.com");
        
        System.out.println("\ne1: " + e1.address());
        System.out.println("e2: " + e2.address());
        System.out.println("Equal: " + e1.equals(e2));
        
    }
}
// Compact constructor for validation
// Concept: compact constructor - validate record creation

record Temperature(double celsius) {
    // Compact constructor for validation
    public Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException(
                "Temperature below absolute zero: " + celsius);
        }
    }
    
    // Add computed methods
    public double fahrenheit() {
        return celsius * 9.0 / 5.0 + 32;
    }
    
    public double kelvin() {
        return celsius + 273.15;
    }
}

record Range(int min, int max) {
    // Compact constructor with normalization
    public Range {
        if (min > max) {
            // Swap if needed
            int temp = min;
            min = max;
            max = temp;
        }
    }
    
    public boolean contains(int value) {
        return value >= min && value <= max;
    }
    
    public int size() {
        return max - min + 1;
    }
}

public class CompactConstructor {
    public static void main(String[] args) {
        // Create temperature with validation
        Temperature t1 = ;
        System.out.println("Temperature: " + t1.celsius() + "°C");
        System.out.println("Fahrenheit: " + t1.fahrenheit() + "°F");
        System.out.println("Kelvin: " + t1.kelvin() + "K");
        
        
        // Validation prevents invalid state
        try {
            Temperature invalid = new Temperature(-300);
            System.out.println("Created: " + invalid);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Range with auto-correction
        Range r1 = new Range(1, 10);
        Range r2 = new Range(10, 1);  // Will be swapped
        
        System.out.println("\nr1: " + r1);
        System.out.println("r2: " + r2);
        System.out.println("r1 equals r2: " + r1.equals(r2));
        
        // Use range methods
        Range range = new Range(5, 15);
        
        System.out.println("\nRange: " + range);
        System.out.println("Size: " + range.size());
        System.out.println("Contains 10: " + range.contains(10));
        System.out.println("Contains 20: " + range.contains(20));
        
        // Multiple validations
        record Email(String address) {
            public Email {
                if (address == null || address.isEmpty()) {
                    throw new IllegalArgumentException("Email cannot be empty");
                }
                if (!address.contains("@")) {
                    throw new IllegalArgumentException("Invalid email format");
                }
                // Normalize to lowercase
                address = address.toLowerCase();
            }
        }
        
        Email e1 = new Email("Alice@Example.COM");
        Email e2 = new Email("alice@example.com");
        
        System.out.println("\ne1: " + e1.address());
        System.out.println("e2: " + e2.address());
        System.out.println("Equal: " + e1.equals(e2));
        
    }
}
// Compact constructor for validation
// Concept: compact constructor - validate record creation

record Temperature(double celsius) {
    // Compact constructor for validation
    public Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException(
                "Temperature below absolute zero: " + celsius);
        }
    }
    
    // Add computed methods
    public double fahrenheit() {
        return celsius * 9.0 / 5.0 + 32;
    }
    
    public double kelvin() {
        return celsius + 273.15;
    }
}

record Range(int min, int max) {
    // Compact constructor with normalization
    public Range {
        if (min > max) {
            // Swap if needed
            int temp = min;
            min = max;
            max = temp;
        }
    }
    
    public boolean contains(int value) {
        return value >= min && value <= max;
    }
    
    public int size() {
        return max - min + 1;
    }
}

public class CompactConstructor {
    public static void main(String[] args) {
        // Create temperature with validation
        Temperature t1 = ;
        System.out.println("Temperature: " + t1.celsius() + "°C");
        System.out.println("Fahrenheit: " + t1.fahrenheit() + "°F");
        System.out.println("Kelvin: " + t1.kelvin() + "K");
        
        
        // Validation prevents invalid state
        try {
            Temperature invalid = new Temperature(-300);
            System.out.println("Created: " + invalid);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Range with auto-correction
        Range r1 = new Range(1, 10);
        Range r2 = new Range(10, 1);  // Will be swapped
        
        System.out.println("\nr1: " + r1);
        System.out.println("r2: " + r2);
        System.out.println("r1 equals r2: " + r1.equals(r2));
        
        // Use range methods
        Range range = new Range(5, 15);
        
        System.out.println("\nRange: " + range);
        System.out.println("Size: " + range.size());
        System.out.println("Contains 10: " + range.contains(10));
        System.out.println("Contains 20: " + range.contains(20));
        
        // Multiple validations
        record Email(String address) {
            public Email {
                if (address == null || address.isEmpty()) {
                    throw new IllegalArgumentException("Email cannot be empty");
                }
                if (!address.contains("@")) {
                    throw new IllegalArgumentException("Invalid email format");
                }
                // Normalize to lowercase
                address = address.toLowerCase();
            }
        }
        
        Email e1 = new Email("Alice@Example.COM");
        Email e2 = new Email("alice@example.com");
        
        System.out.println("\ne1: " + e1.address());
        System.out.println("e2: " + e2.address());
        System.out.println("Equal: " + e1.equals(e2));
        
    }
}

Constructor without parameters - validates/normalizes before assignment.

compact constructor `record Point(int x, int y) { Point { if (x < 0) throw...; } }` - validation.

Custom methods

Add your own methods to records.

CustomMethods.java
// Custom methods in records
// Concept: custom methods - add behavior to records

record Circle(double radius) {
    // Add custom methods
    public double area() {
        return Math.PI * radius * radius;
    }
    
    public double circumference() {
        return 2 * Math.PI * radius;
    }
    
    public double diameter() {
        return 2 * radius;
    }
    
    // Static factory method
    public static Circle fromDiameter(double diameter) {
        return new Circle(diameter / 2);
    }
}

record Money(double amount, String currency) {
    // Methods with logic
    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(amount + other.amount, currency);
    }
    
    public Money multiply(double factor) {
        return new Money(amount * factor, currency);
    }
    
    public String formatted() {
        return String.format("%.2f %s", amount, currency);
    }
}

public class CustomMethods {
    public static void main(String[] args) {
        // Use custom methods
        Circle c1 = ;
        
        System.out.println("Circle: " + c1);
        System.out.println("Radius: " + c1.radius());
        System.out.printf("Area: %.2f%n", c1.area());
        System.out.printf("Circumference: %.2f%n", c1.circumference());
        System.out.printf("Diameter: %.2f%n", c1.diameter());
        
        
        // Use static factory
        Circle c2 = Circle.fromDiameter(20);
        System.out.println("\nFrom diameter 20: " + c2);
        System.out.printf("Radius: %.1f%n", c2.radius());
        
        // Money operations
        Money price1 = new Money(10.50, "USD");
        Money price2 = new Money(5.25, "USD");
        
        System.out.println("\nPrice 1: " + price1.formatted());
        System.out.println("Price 2: " + price2.formatted());
        
        Money total = price1.add(price2);
        System.out.println("Total: " + total.formatted());
        
        Money doubled = price1.multiply(2);
        System.out.println("Doubled: " + doubled.formatted());
        
        // Currency mismatch
        try {
            Money euros = new Money(10, "EUR");
            Money combined = price1.add(euros);
            System.out.println("Combined: " + combined);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Complex record with methods
        record Rectangle(double width, double height) {
            public double area() {
                return width * height;
            }
            
            public double perimeter() {
                return 2 * (width + height);
            }
            
            public boolean isSquare() {
                return width == height;
            }
            
            public Rectangle scale(double factor) {
                return new Rectangle(width * factor, height * factor);
            }
        }
        
        Rectangle rect = new Rectangle(10, 5);
        
        System.out.println("\nRectangle: " + rect);
        System.out.printf("Area: %.1f%n", rect.area());
        System.out.printf("Perimeter: %.1f%n", rect.perimeter());
        System.out.println("Is square: " + rect.isSquare());
        
        Rectangle scaled = rect.scale(2);
        System.out.println("Scaled 2x: " + scaled);
        
    }
}
// Custom methods in records
// Concept: custom methods - add behavior to records

record Circle(double radius) {
    // Add custom methods
    public double area() {
        return Math.PI * radius * radius;
    }
    
    public double circumference() {
        return 2 * Math.PI * radius;
    }
    
    public double diameter() {
        return 2 * radius;
    }
    
    // Static factory method
    public static Circle fromDiameter(double diameter) {
        return new Circle(diameter / 2);
    }
}

record Money(double amount, String currency) {
    // Methods with logic
    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(amount + other.amount, currency);
    }
    
    public Money multiply(double factor) {
        return new Money(amount * factor, currency);
    }
    
    public String formatted() {
        return String.format("%.2f %s", amount, currency);
    }
}

public class CustomMethods {
    public static void main(String[] args) {
        // Use custom methods
        Circle c1 = ;
        
        System.out.println("Circle: " + c1);
        System.out.println("Radius: " + c1.radius());
        System.out.printf("Area: %.2f%n", c1.area());
        System.out.printf("Circumference: %.2f%n", c1.circumference());
        System.out.printf("Diameter: %.2f%n", c1.diameter());
        
        
        // Use static factory
        Circle c2 = Circle.fromDiameter(20);
        System.out.println("\nFrom diameter 20: " + c2);
        System.out.printf("Radius: %.1f%n", c2.radius());
        
        // Money operations
        Money price1 = new Money(10.50, "USD");
        Money price2 = new Money(5.25, "USD");
        
        System.out.println("\nPrice 1: " + price1.formatted());
        System.out.println("Price 2: " + price2.formatted());
        
        Money total = price1.add(price2);
        System.out.println("Total: " + total.formatted());
        
        Money doubled = price1.multiply(2);
        System.out.println("Doubled: " + doubled.formatted());
        
        // Currency mismatch
        try {
            Money euros = new Money(10, "EUR");
            Money combined = price1.add(euros);
            System.out.println("Combined: " + combined);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Complex record with methods
        record Rectangle(double width, double height) {
            public double area() {
                return width * height;
            }
            
            public double perimeter() {
                return 2 * (width + height);
            }
            
            public boolean isSquare() {
                return width == height;
            }
            
            public Rectangle scale(double factor) {
                return new Rectangle(width * factor, height * factor);
            }
        }
        
        Rectangle rect = new Rectangle(10, 5);
        
        System.out.println("\nRectangle: " + rect);
        System.out.printf("Area: %.1f%n", rect.area());
        System.out.printf("Perimeter: %.1f%n", rect.perimeter());
        System.out.println("Is square: " + rect.isSquare());
        
        Rectangle scaled = rect.scale(2);
        System.out.println("Scaled 2x: " + scaled);
        
    }
}
// Custom methods in records
// Concept: custom methods - add behavior to records

record Circle(double radius) {
    // Add custom methods
    public double area() {
        return Math.PI * radius * radius;
    }
    
    public double circumference() {
        return 2 * Math.PI * radius;
    }
    
    public double diameter() {
        return 2 * radius;
    }
    
    // Static factory method
    public static Circle fromDiameter(double diameter) {
        return new Circle(diameter / 2);
    }
}

record Money(double amount, String currency) {
    // Methods with logic
    public Money add(Money other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(amount + other.amount, currency);
    }
    
    public Money multiply(double factor) {
        return new Money(amount * factor, currency);
    }
    
    public String formatted() {
        return String.format("%.2f %s", amount, currency);
    }
}

public class CustomMethods {
    public static void main(String[] args) {
        // Use custom methods
        Circle c1 = ;
        
        System.out.println("Circle: " + c1);
        System.out.println("Radius: " + c1.radius());
        System.out.printf("Area: %.2f%n", c1.area());
        System.out.printf("Circumference: %.2f%n", c1.circumference());
        System.out.printf("Diameter: %.2f%n", c1.diameter());
        
        
        // Use static factory
        Circle c2 = Circle.fromDiameter(20);
        System.out.println("\nFrom diameter 20: " + c2);
        System.out.printf("Radius: %.1f%n", c2.radius());
        
        // Money operations
        Money price1 = new Money(10.50, "USD");
        Money price2 = new Money(5.25, "USD");
        
        System.out.println("\nPrice 1: " + price1.formatted());
        System.out.println("Price 2: " + price2.formatted());
        
        Money total = price1.add(price2);
        System.out.println("Total: " + total.formatted());
        
        Money doubled = price1.multiply(2);
        System.out.println("Doubled: " + doubled.formatted());
        
        // Currency mismatch
        try {
            Money euros = new Money(10, "EUR");
            Money combined = price1.add(euros);
            System.out.println("Combined: " + combined);
        } catch (IllegalArgumentException e) {
            System.out.println("\nError: " + e.getMessage());
        }
        
        // Complex record with methods
        record Rectangle(double width, double height) {
            public double area() {
                return width * height;
            }
            
            public double perimeter() {
                return 2 * (width + height);
            }
            
            public boolean isSquare() {
                return width == height;
            }
            
            public Rectangle scale(double factor) {
                return new Rectangle(width * factor, height * factor);
            }
        }
        
        Rectangle rect = new Rectangle(10, 5);
        
        System.out.println("\nRectangle: " + rect);
        System.out.printf("Area: %.1f%n", rect.area());
        System.out.printf("Perimeter: %.1f%n", rect.perimeter());
        System.out.println("Is square: " + rect.isSquare());
        
        Rectangle scaled = rect.scale(2);
        System.out.println("Scaled 2x: " + scaled);
        
    }
}

Records can have static and instance methods. Fields stay immutable.

Record vs class

When to use each.

RecordVsClass.java
// Record vs class comparison
// Concept: record vs class - when to use each

// Traditional class
class MutablePoint {
    private int x;
    private int y;
    
    public MutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() { return x; }
    public int getY() { return y; }
    
    public void setX(int x) { this.x = x; }
    public void setY(int y) { this.y = y; }
    
    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }
}

// Record (immutable)
record ImmutablePoint(int x, int y) {
    public ImmutablePoint move(int dx, int dy) {
        return new ImmutablePoint(x + dx, y + dy);
    }
}

// Class with inheritance
class Shape {
    protected String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    public String getColor() {
        return color;
    }
}

class ColoredCircle extends Shape {
    private double radius;
    
    public ColoredCircle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    public double getRadius() {
        return radius;
    }
}

// Record implementing interface
interface Describable {
    String describe();
}

record Product(String name, double price) implements Describable {
    @Override
    public String describe() {
        return name + " ($" + price + ")";
    }
}

public class RecordVsClass {
    public static void main(String[] args) {
        // Mutable class
        MutablePoint mp = new MutablePoint(10, 20);
        System.out.println("Mutable point: (" + mp.getX() + ", " + mp.getY() + ")");
        
        mp.setX(15);
        mp.move(5, 10);
        System.out.println("After changes: (" + mp.getX() + ", " + mp.getY() + ")");
        
        
        // Immutable record
        ImmutablePoint ip = new ImmutablePoint(10, 20);
        System.out.println("\nImmutable point: " + ip);
        
        ImmutablePoint ip2 = ip.move(5, 10);
        System.out.println("Original: " + ip);
        System.out.println("After move: " + ip2);
        
        // Inheritance example
        ColoredCircle circle = new ColoredCircle("red", 5.0);
        System.out.println("\nColored circle:");
        System.out.println("  Color: " + circle.getColor());
        System.out.println("  Radius: " + circle.getRadius());
        
        // Records cannot extend classes (always final)
        // record cannot be used here
        
        // Record with interface
        Product product = new Product("Laptop", 999.99);
        System.out.println("\nProduct: " + product.describe());
        
        // When to use records
        System.out.println("\nUse records for:");
        System.out.println("  ✓ DTOs (Data Transfer Objects)");
        System.out.println("  ✓ Immutable data models");
        System.out.println("  ✓ Return multiple values");
        System.out.println("  ✓ Map keys, set elements");
        
        System.out.println("\nUse classes for:");
        System.out.println("  ✓ Mutable state");
        System.out.println("  ✓ Inheritance hierarchies");
        System.out.println("  ✓ Private fields with logic");
        System.out.println("  ✓ Complex initialization");
        
        // Practical examples
        // Good use of record
        record ApiResponse(int status, String message, Object data) {}
        
        ApiResponse success = new ApiResponse(200, "OK", "Hello");
        ApiResponse error = new ApiResponse(404, "Not Found", null);
        
        System.out.println("\nAPI responses:");
        System.out.println("  " + success);
        System.out.println("  " + error);
        
        // Good use of class
        class Counter {
            private int count = 0;
            
            public void increment() { count++; }
            public int getCount() { return count; }
        }
        
        Counter counter = new Counter();
        counter.increment();
        counter.increment();
        System.out.println("\nCounter: " + counter.getCount());
    }
}

Record: pure data, immutable. Class: mutable state, complex behavior.

Exercise: Practical.java

Model API responses with records