Can we extend Java record

No — Java records cannot be extended. They are implicitly final and cannot be subclassed. Attempting to extend a record causes a compile error. This is a deliberate design decision that will not change in future versions.


Proof: Compile‑Time Error

Consider a simple record and a naive attempt to extend it:

record MobilePhone(String brand, String model, int osVersion) {}

// Compile error: cannot inherit from final 'MobilePhone'
class SmartPhone extends MobilePhone {
}

The compiler outputs:

Error: Cannot inherit from final 'MobilePhone'

This is because records are implicitly final – just like enums. The Java Language Specification (§8.1.1.1) explicitly states that a record declaration is implicitly final.


Why Are Records Final? The Design Rationale

The designers of Java (JEP 395) intentionally made records final for several compelling reasons:

  • Component integrity – a record is a transparent carrier of its state components. Allowing subclasses to add extra fields would break the guarantee that “the record’s state is exactly its components.”
  • equals() and hashCode() consistency – the generated equals() compares only the declared components. A subclass with additional fields would violate the Liskov Substitution Principle if it inherited the parent’s equals().
  • Pattern matching and exhaustiveness – sealed type hierarchies and pattern matching rely on knowing all possible subtypes at compile time. If records could be extended arbitrarily, exhaustive switch expressions would become impossible to verify.
  • Simplicity and predictability – records are designed to be simple, value‑based data carriers. Extensibility would add complexity and blur the line between data and behaviour.

The official JEP 395 states: “Records are intended to be simple data carriers. They are not intended to be extended or to serve as building blocks for complex inheritance hierarchies.”


Verifying with Reflection

You can programmatically check that a record is final and that it extends java.lang.Record:

import java.lang.reflect.Modifier;

record MobilePhone(String brand, String model, int osVersion) {}

public class RecordCheck {
    public static void main(String[] args) {
        Class<MobilePhone> clazz = MobilePhone.class;

        System.out.println("Is final:  " + Modifier.isFinal(clazz.getModifiers())); // true
        System.out.println("Is record: " + clazz.isRecord());                       // true
        System.out.println("Superclass: " + clazz.getSuperclass());                // class java.lang.Record
    }
}

The isRecord() method (Java 16+) returns true for any record type. All records implicitly extend java.lang.Record – which is why they also cannot explicitly extend any other class.


What Records Can Do Instead: Implement Interfaces

While records cannot extend classes, they can implement interfaces. This is the primary mechanism for sharing behaviour across records:

interface Describable {
    String describe();
}

record MobilePhone(String brand, String model, int osVersion) implements Describable {
    @Override
    public String describe() {
        return brand + " " + model + " (OS v" + osVersion + ")";
    }
}

record Tablet(String brand, String model, boolean hasStylusSupport) implements Describable {
    @Override
    public String describe() {
        return brand + " " + model + (hasStylusSupport ? " [Stylus]" : "");
    }
}

Describable phone  = new MobilePhone("Samsung", "Galaxy S24", 14);
Describable tablet = new Tablet("Apple", "iPad Pro", true);

System.out.println(phone.describe());  // Samsung Galaxy S24 (OS v14)
System.out.println(tablet.describe()); // Apple iPad Pro [Stylus]

Composition Over Inheritance: Records Containing Records

When you want to build richer data structures, compose records instead of extending them:

record Address(String street, String city, String country) {}

record Person(String name, int age, Address address) {}

Person person = new Person(
    "Alice",
    30,
    new Address("1044 Main St", "Springfield", "USA")
);

System.out.println(person.name());               // Alice
System.out.println(person.address().city());     // Springfield
System.out.println(person);
// Person[name=Alice, age=30, address=Address[street=1044 Main St, city=Springfield, country=USA]]

Composition gives you the same structural relationship as inheritance without violating the record contract – and it’s often more flexible.


Sealed Interfaces + Records: A Powerful Combination

Records pair naturally with sealed interfaces to model closed type hierarchies – exactly where inheritance would have been tempting, but records give a cleaner solution:

sealed interface Device permits MobilePhone, Tablet, Laptop {}

record MobilePhone(String brand, String model) implements Device {}
record Tablet(String brand, boolean hasPen)    implements Device {}
record Laptop(String brand, int ramGb)         implements Device {}

// Exhaustive switch – the compiler verifies all cases are covered
String describeDevice(Device d) {
    return switch (d) {
        case MobilePhone p -> "Phone: " + p.brand() + " " + p.model();
        case Tablet t      -> "Tablet: " + t.brand() + (t.hasPen() ? " (with pen)" : "");
        case Laptop l      -> "Laptop: " + l.brand() + " (" + l.ramGb() + "GB RAM)";
    };
}

This pattern is increasingly popular in modern Java (especially with pattern matching in Java 21+) and provides exhaustive, type‑safe branching.


Comparison with Other Languages

Records are not the only immutable data carriers in the JVM ecosystem:

Language / FeatureExtensible?ImmutabilityNotes
Java Records ❌ No (implicitly final) ✅ Yes (all components final) Built into Java 16+
Kotlin Data Classes ❌ No (by default, can be open with care) ⚠️ Components can be var (mutable) or val More flexible, but can break equals() contract if extended
Scala Case Classes ❌ No (implicitly final, can be abstract but rarely) ✅ Yes (parameters are val by default) Similar to records, but with more features
Lombok @Data ✅ Yes (standard class) ❌ No (generates setters) Not a language feature, requires annotation processing

Can I Work Around This Limitation?

Since records are final, you cannot extend them directly. However, you have several alternatives:

  • Use a wrapper class – create a regular class that holds a record as a field. This gives you extensibility but introduces extra boilerplate.
  • Use a sealed hierarchy – define a sealed interface or abstract class and let each implementation be a record. This models a closed family of types without inheritance from a record.
  • Refactor to composition – if you need additional data, compose records inside larger records.
  • Use interfaces – define behaviour in interfaces, implement them in multiple records.

The wrapper approach is not recommended for most cases because it breaks the simplicity of records and often adds unnecessary complexity. The sealed‑interface approach is the idiomatic way to model a closed set of variants.


Best Practices

  • Accept that records are final – design your data models with composition and interfaces in mind.
  • Use sealed interfaces with records for domain hierarchies that would otherwise tempt you to extend records.
  • Prefer composition over inheritance – nest records inside records to build complex aggregates.
  • Implement interfaces to share behaviour across records.
  • Do not try to extend records via bytecode manipulation – it will break the invariants and may cause runtime errors.

Summary

Java records are implicitly final and cannot be extended – by design. This ensures component integrity, consistent equals()/hashCode(), and enables exhaustive pattern matching. Instead of inheritance, use interfaces to share behaviour, composition to build aggregates, and sealed interfaces to model closed type hierarchies. The language gives you all the tools you need to design rich, safe, and maintainable data models – just not through subclassing records.

Embrace the finality – it’s a feature, not a bug.


Happy coding!

No comments :

Post a Comment

Please leave your message queries or suggetions.

Note: Only a member of this blog may post a comment.