CodeGym /Courses /JAVA 25 SELF /Differences between record and class, record limitations

Differences between record and class, record limitations

JAVA 25 SELF
Level 22 , Lesson 4
Available

1. Comparison of record and class: what are the key differences?

In Java, there are two main ways to define your data types: using regular classes (class) and record classes (record). At first glance, both options let you store and process data. But if you dig a little deeper, there are more differences than you might think!

Comparison table: class vs record

Characteristic Regular class (class) Record class (record)
Mutability Any: fields can be final or not Immutable: all fields are final
Inheritance Can extend (extends), not final by default Always final, cannot be a superclass
Fields Any: static, instance, final or non-final, any types Only record components (private final), plus static fields
Getters/setters We write them ourselves (or generate with Lombok) Getters are generated automatically (method name equals the field name), no setters
equals/hashCode/toString Usually written/generated manually (equals, hashCode, toString) Generated automatically over all components
Constructors Any number, any shape One canonical (over all components), you can add a compact constructor
Interfaces Can implement Can implement
Additional methods Any You can add methods only (no fields)
Use in collections Possible, but you must correctly implement equals/hashCode Ideal for keys/values; everything is already implemented

Example for clarity

Regular class:


public class Person {
    private final String name;
    private final int age;

    public Person(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) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

Record class:


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

That’s it! One line of code — and you get the same thing (and even better). And no risk of forgetting to implement something important.

2. Record class limitations

Record classes are not just “short syntax” but a distinct concept with strict rules. Let’s take a closer look at them.

A record is always final

A record class is by definition always final. This means you cannot create a subclass of a record:


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

// public class ColoredPoint extends Point { } // Compilation error!

If you need to extend behavior, use regular classes or composition (embed the record in a class).

A record cannot be a superclass

A record class cannot be a parent for other classes; it is always final. That’s logical: if it were possible, someone could add a mutable field — and the whole “immutable data” concept would fall apart.

Only final fields (components)

All record components are declared in the header and are private final by default. You cannot add instance fields in the body of a record:


public record User(String login, String email) {
    // int counter; // Error! Non-static fields are not allowed
    static int totalUsers = 0; // Allowed, this is a static field
}

No setters

A record class cannot have setters for its components. Any attempt to add a method like setX(int x) will be pointless: you can’t change the value of a field after the object is created.


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // Error: cannot modify a final field
}

No no-arg constructor

A record class always has only the canonical constructor that accepts values for all components. You cannot create a record without providing all data:


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // Error: no no-arg constructor

No instance initializers

A record class cannot contain instance initializers (those written in curly braces outside of methods):


public record User(String login) {
    // { /* ... */ } // Error: instance initializers are forbidden
}

Inheritance constraints

A record class cannot explicitly extend another class (except java.lang.Record, which is the hidden base class for all records). But implementing interfaces — absolutely!


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("Printing book: " + title);
    }
}

Not suited for complex business logic

A record is about data, not behavior. If your object has complex logic, mutable state, a “life cycle”, or a bunch of dependencies — a record won’t help. Prefer a regular class.

3. When should you use record classes?

  • DTO (Data Transfer Object): to pass immutable data between application layers, services, microservices, or REST controllers (for example, in JSON responses).
  • Value Object: objects that are defined solely by their values.
  • Keys and values in collections: when a correct implementation of equals and hashCode matters (for example, when used in HashMap or Set).
  • Computation results: when you need to return multiple values from a method (for example, record Pair<T, U>(T first, U second)).

Example: DTO for a REST controller


public record UserDto(String login, String email) { }

You can now safely return this type from a controller without worrying that someone will mutate its fields.

Example: Key for HashMap


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

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// Everything works correctly: equals and hashCode are already implemented!

4. When NOT to use record classes

  • Mutable state: if at least one field must change after the object is created.
  • Complex logic: if the object has complex behavior, many methods, nested objects with mutable state.
  • Inheritance: if you need a class hierarchy, abstract base classes, method overriding.
  • Domain entities: for example, objects that live in a database and have a unique identifier.

Example: when a regular class is needed


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

It’s clear here that the object’s state changes — a record is not suitable.

5. Practical examples: choosing between record and class

Example 1: record — the perfect choice


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • A rectangle is defined only by width and height.
  • There’s no need to change these values after creation.
  • You can add a useful method like area().
  • Java will do the rest for you.

Example 2: class — the better option


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

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

Need to change the rectangle’s dimensions after creation? Use a regular class.

6. Typical mistakes when working with record classes

Error No. 1: attempting to add an instance field.
A record class does not allow declaring instance fields outside the component list. If you try, the compiler will report an error. For example:


public record City(String name) {
    // int population; // Error!
}

Error No. 2: wanting to add a setter.
A record does not support setters for its components. Any attempt to change a field’s value after the object is created results in a compilation error.

Error No. 3: attempting to extend a record or extend from a record.
A record is always final. You cannot extend a record, and a record cannot extend another class (except the hidden java.lang.Record).

Error No. 4: using a record for mutable objects.
If you plan to change an object’s state after creation, a record is not for you. Use a regular class.

Error No. 5: forgetting constructor constraints.
A record class must have a constructor that accepts values for all components. There is no no-arg constructor!

1
Task
JAVA 25 SELF, level 22, lesson 4
Locked
Cataloging your home library 📚
Cataloging your home library 📚
1
Task
JAVA 25 SELF, level 22, lesson 4
Locked
Checking Coordinate Matches on the Map 🗺️
Checking Coordinate Matches on the Map 🗺️
1
Task
JAVA 25 SELF, level 22, lesson 4
Locked
Print document content 📄
Print document content 📄
1
Task
JAVA 25 SELF, level 22, lesson 4
Locked
Comparing mutable and immutable user profiles 🧑‍🤝‍🧑
Comparing mutable and immutable user profiles 🧑‍🤝‍🧑
1
Survey/quiz
Record classes, level 22, lesson 4
Unavailable
Record classes
Record classes
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION