Regular Java Class (Before Java 16)

public class User {
    private final String name;
    private final int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && name.equals(user.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + '}';
    }
}

This works — but it’s a lot of code just to hold data.


Java Record (Java 16+)

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

That’s it! Java will auto-generate:


Usage Example (Same for both)

User user1 = new User("Alice", 25);
User user2 = new User("Alice", 25);
System.out.println(user1.name());         // "Alice"
System.out.println(user1.equals(user2));  // true
System.out.println(user1);                // User[name=Alice, age=25]

Note: In records, you use name() instead of getName().


Summary

Feature Class Record
Boilerplate Lots of code One-liner
Immutability Manual Default
Inheritance Allowed (extends) Not allowed (only implements)
Use Case General purpose Data-only classes
class Animal {}
record Dog(String name) extends Animal {} // ERROR

interface Animal {
    void sound();
}

record Dog(String name) implements Animal {

    @Override
    public void sound() {
        System.out.println("Woof!");
    }
}