📋 Section 20 · Question #3

Can You Inherit From Record or Have Record Inherit From Another Class

While class inheritance is forbidden, a Record can implement any number of interfaces:


🟢 Junior Level

No to both. Java imposes two categorical restrictions regarding class inheritance with records:

  1. You cannot inherit FROM a Record: Every record is implicitly final. Attempting to declare a class or another record that extends an existing record triggers a compilation error.
  2. A Record cannot extend another class: All records implicitly extend java.lang.Record directly. Because Java does not support multiple class inheritance, using the extends keyword with a record is prohibited.
public record Point(int x, int y) {}

// ❌ Compilation error: Cannot inherit from final 'Point'
public class ColoredPoint extends Point {} 

// ❌ Compilation error: Records cannot extend other classes
public record MyRecord(int a) extends SomeClass {} 

// ❌ Compilation error: Records cannot extend java.lang.Record explicitly
public record MyRecord(int a) extends Record {} 

What IS Permitted: Implementing Interfaces

While class inheritance is forbidden, a Record can implement any number of interfaces:

public record User(String name, String email) implements Serializable, Comparable<User> {
    @Override
    public int compareTo(User o) {
        return this.name.compareTo(o.name);
    }
}

🟡 Middle Level

Architectural Rationale Behind the Restrictions

The ban on class inheritance in Records is a deliberate language design decision by OpenJDK architects:

  1. State-is-the-Header Principle: The state of a record is defined exclusively and transparently by its header. If subclassing were permitted, a child class could declare additional instance fields, destroying the contract that “the header is the state”.
  2. Immutability Invariant: A subclass could introduce mutable fields or override accessor methods with stateful or fluctuating behavior.
  3. Preserving equals() and hashCode() Contracts: The classic object-oriented equivalence pitfall (detailed in Joshua Bloch’s Effective Java, Item 10) proves that it is mathematically impossible to preserve equals() symmetry and transitivity if subclasses add value components. For records, equality is strictly mathematical: two records are equal if and only if all their header components are equal.
  4. JIT Compiler Optimization: Knowing that records are final and structurally immutable allows the HotSpot JIT compiler to aggressively inline accessors, optimize scalar replacement via Escape Analysis, and prepare for future flat value types in Project Valhalla.

Replacing Inheritance: Composition over Inheritance

Instead of class inheritance hierarchies, modern Java relies on composition and delegation:

// ❌ Anti-pattern: Attempting to build an inheritance hierarchy
// record AuditedOrder(...) extends BaseAuditInfo {} // Compilation error!

// ✅ Idiomatic pattern: Component composition
public record AuditMetadata(Instant createdAt, String createdBy) {}

public record Order(
    UUID orderId,
    BigDecimal amount,
    AuditMetadata audit // Embedded immutable component
) {}

🔴 Senior Level

Algebraic Data Types (ADT): Sealed Interfaces + Records

In modern Java (Java 17+), classical hierarchical inheritance for domain models is replaced by Algebraic Data Types (ADTs):

  • Product Type: Modeled via record ($A \times B$: an order consists of an ID and an amount).
  • Sum Type: Modeled via sealed interface ($A + B + C$: a payment method is either card, or cash, or crypto).
public sealed interface PaymentMethod permits CardPayment, CashPayment, CryptoPayment {}

public record CardPayment(String cardNumber, String cvv) implements PaymentMethod {}
public record CashPayment(BigDecimal tenderedAmount) implements PaymentMethod {}
public record CryptoPayment(String walletAddress, String txHash) implements PaymentMethod {}

Exhaustive Pattern Matching in Java 21+

Because the compiler knows all permitted implementations of a sealed interface, switch pattern matching requires no default branch, providing compile-time exhaustiveness checking:

public String processPayment(PaymentMethod method) {
    return switch (method) {
        case CardPayment(var num, var cvv) -> "Processing card: " + mask(num);
        case CashPayment(var amount)       -> "Cash received: " + amount;
        case CryptoPayment(var addr, var tx) -> "Crypto transfer: " + tx;
        // No default needed: compiler guarantees exhaustive coverage
    };
}

JVM Specification Constraints (JVMS)

The inheritance ban is enforced at both compiler and JVM runtime levels:

  1. Access Flags: The compiled bytecode must contain both ACC_FINAL and ACC_RECORD flags.
  2. Superclass Requirement: Under JVMS §4.1, the superclass entry in the constant pool must be strictly java/lang/Record.
  3. Regular Classes Cannot Extend Record: Under JLS §8.1.4, attempting to compile an ordinary class extending java.lang.Record fails.
  4. ClassLoader Verification: If bytecode manipulation tools (ASM, ByteBuddy) attempt to synthesize an ordinary class inheriting from java.lang.Record, the JVM verifier rejects the class at link time with a ClassFormatError or VerifyError.

4 Tricky Questions

1. Can a regular Java class explicitly extend java.lang.Record (public class MyClass extends java.lang.Record)?

Answer: No, this is strictly forbidden by the Java Language Specification (JLS §8.1.4). The compiler produces the error: "The type java.lang.Record cannot be the superclass of MyClass; only records can extend java.lang.Record". Furthermore, this constraint is enforced by the JVM bytecode verifier. If manipulated bytecode tries to load a non-record class whose superclass is java/lang/Record, the class loader throws a fatal ClassFormatError or VerifyError.

2. Can a Record be declared with abstract, sealed, or non-sealed modifiers?

Answer:

  • abstract: Prohibited. Every record is implicitly final and contains complete implementations for all components; final abstract is illegal.
  • sealed / non-sealed: Prohibited. Because a record is final and cannot have subclasses, inheritance control modifiers are meaningless on record declarations. However, a record can serve as a permitted leaf in a sealed interface hierarchy:
    public sealed interface Result<T> permits Success, Failure {}
    public record Success<T>(T data) implements Result<T> {}
    public record Failure<T>(Throwable error) implements Result<T> {}
    

3. How do Algebraic Data Types (ADTs) using Records and Sealed Interfaces replace traditional class inheritance?

Answer: Traditional class inheritance represents open polymorphism: base classes can be extended arbitrarily across modules, and behavior is dispersed across virtual methods. In contrast, ADTs represent closed type systems:

  • A sealed interface defines the closed universe of all possible system states (Sum Type).
  • A record defines the fixed, immutable structure for each concrete variant (Product Type).
  • Operations and behaviors are defined externally via pattern matching and pure functions. When a new variant is introduced to the sealed hierarchy, the compiler forces every switch expression in the codebase to handle the new case, preventing unhandled edge cases.

4. If two Records have identical components (record PointA(int x, int y) and record PointB(int x, int y)), can they be used interchangeably without a common interface?

Answer: No, they cannot. Java is a nominal type system, not a structural (duck-typed) system. Even though PointA and PointB share identical memory layouts, component names, and types, they are two completely distinct, incompatible types in the JVM type system. You cannot assign an instance of PointA to a variable of type PointB without explicit mapping. Polymorphic interchangeability requires declaring a common interface:

public interface Coordinates { int x(); int y(); }
public record PointA(int x, int y) implements Coordinates {}
public record PointB(int x, int y) implements Coordinates {}

🎯 Interview Cheat Sheet

Summary in 60 Seconds

  • Record $\to$ Class: A record cannot extend any class (it already implicitly extends java.lang.Record).
  • Class $\to$ Record: No class or record can extend a record (all records are implicitly final).
  • Class $\to$ java.lang.Record: Regular classes are forbidden from extending java.lang.Record.
  • Interfaces: Records can implement any number of interfaces (implements InterfaceA, InterfaceB).
  • Modifiers: Records cannot be abstract, sealed, or non-sealed.
  • Modern Alternative: Use Composition and Algebraic Data Types (sealed interface + record) with Java 21 Pattern Matching.

Common Interview Traps

  • Claiming records can be subclassed if declared non-final (records are always implicitly final).
  • Believing java.lang.Object is the direct superclass of a record (direct superclass is java.lang.Record).
  • Trying to declare an abstract record to share common components across multiple records.