Inheritance

How Java inheritance works — extends, super, method overriding, and when to use inheritance vs composition in real enterprise projects.

Last reviewed: January 2025

·Java 8Java 11Java 17Java 21
✅
Quick Understanding: Inheritance lets one class (child/subclass) acquire the fields and methods of another class (parent/superclass) using the extends keyword. A Dog extends Animal — Dog inherits all Animal behavior and can add its own. Java supports single inheritance for classes but multiple inheritance through interfaces.

🔍 What Is It?

Inheritance Hierarchy

Object (root of all Java classes)
  └── Animal
        ├── Dog
        └── Cat

Key Keywords:

  • →extends — inherit from a class (single inheritance only)
  • →implements — implement one or more interfaces
  • →super — access parent class constructor or methods
  • →@Override — marks a method that overrides a parent method
  • →final — on a class: cannot be subclassed; on a method: cannot be overridden
  • →abstract — class cannot be instantiated; method must be overridden

What is Inherited:

  • →Public and protected fields and methods
  • →Package-private members (if same package)

What is NOT Inherited:

  • →Private members
  • →Constructors (but you can call them with super())
  • →Static members (they belong to the class, not instances)

🎯 Why Do We Need It?

Inheritance solves code reuse in hierarchical relationships:

  • →Is-a relationship — Dog IS-A Animal; SavingsAccount IS-A BankAccount
  • →Code reuse — common behavior defined once in parent, inherited by all children
  • →Polymorphism — treat subclasses uniformly through parent type reference
  • →Framework extension — Spring, Hibernate, and most frameworks use inheritance for customization

However, inheritance is often overused. The modern rule: prefer composition over inheritance unless there is a clear IS-A relationship.

🏗️ What Happens in Real Projects?

In enterprise Spring Boot projects:

// Base entity with common audit fields
@MappedSuperclass
public abstract class BaseEntity {
    @Id @GeneratedValue
    private Long id;
    
    @CreatedDate
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    private LocalDateTime updatedAt;
}

// All entities inherit audit fields
@Entity
public class Order extends BaseEntity { ... }

@Entity
public class Product extends BaseEntity { ... }

This is a legitimate use of inheritance — all entities genuinely share audit behavior.

⚙️ How It Works

Constructor Chaining:

When you create a subclass object, the parent constructor runs first. If you don't call super() explicitly, Java calls the no-arg parent constructor automatically. If the parent has no no-arg constructor, you MUST call super(args) explicitly.

Method Resolution (Dynamic Dispatch):

At runtime, the JVM calls the most specific override. Even if the reference type is Animal, if the actual object is a Dog, Dog's method is called.

Method Hiding (static methods):

Static methods are NOT polymorphic. If a subclass defines a static method with the same signature, it hides the parent's static method — it does not override it.

Sealed Classes (Java 17+):

sealed class Shape permits Circle, Rectangle, Triangle

Restricts which classes can extend Shape — useful for exhaustive pattern matching.

💻 Example

Inheritance in an Enterprise Payment System — A payment system showing when inheritance is appropriate and when to use composition.

java
// Abstract base class — defines the contract
public abstract class Payment {
    protected final String paymentId;
    protected final BigDecimal amount;
    protected final String currency;
    protected PaymentStatus status;
    
    protected Payment(String paymentId, BigDecimal amount, String currency) {
        this.paymentId = paymentId;
        this.amount = amount;
        this.currency = currency;
        this.status = PaymentStatus.PENDING;
    }
    
    // Template method pattern — defines the algorithm structure
    public final PaymentResult process() {
        validate();                    // common validation
        PaymentResult result = execute(); // subclass-specific execution
        postProcess(result);           // common post-processing
        return result;
    }
    
    // Common validation for all payment types
    protected void validate() {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Payment amount must be positive");
        }
    }
    
    // Each subclass implements its own execution
    protected abstract PaymentResult execute();
    
    // Hook method — subclasses can override if needed
    protected void postProcess(PaymentResult result) {
        this.status = result.isSuccess() ? PaymentStatus.COMPLETED : PaymentStatus.FAILED;
    }
    
    public String getPaymentId() { return paymentId; }
    public BigDecimal getAmount() { return amount; }
    public PaymentStatus getStatus() { return status; }
}

// Concrete subclass
public class CreditCardPayment extends Payment {
    private final String cardToken;
    private final String cvv;
    
    public CreditCardPayment(String paymentId, BigDecimal amount,
                              String currency, String cardToken, String cvv) {
        super(paymentId, amount, currency);
        this.cardToken = cardToken;
        this.cvv = cvv;
    }
    
    @Override
    protected void validate() {
        super.validate();  // call parent validation first
        if (cardToken == null || cardToken.isBlank()) {
            throw new IllegalArgumentException("Card token required");
        }
    }
    
    @Override
    protected PaymentResult execute() {
        // Call payment gateway API
        return paymentGateway.charge(cardToken, amount, currency);
    }
}

// Another subclass
public class BankTransferPayment extends Payment {
    private final String iban;
    
    public BankTransferPayment(String paymentId, BigDecimal amount,
                                String currency, String iban) {
        super(paymentId, amount, currency);
        this.iban = iban;
    }
    
    @Override
    protected PaymentResult execute() {
        return bankService.transfer(iban, amount, currency);
    }
}

🤔 Common Confusions

❓ When should I use inheritance vs composition?

Use inheritance for IS-A relationships where the subclass is genuinely a specialized version of the parent (Dog IS-A Animal, SavingsAccount IS-A BankAccount). Use composition for HAS-A relationships (Car HAS-A Engine). The rule of thumb: if you cannot say "SubClass IS-A ParentClass" naturally, use composition. Composition is more flexible and avoids tight coupling.

❓ What is the difference between method overriding and method hiding?

Overriding applies to instance methods — the most specific implementation is called based on the actual runtime type (polymorphism). Hiding applies to static methods — the method called depends on the declared reference type, not the runtime type. Static methods are not polymorphic. Always annotate overrides with @Override to catch mistakes.

❓ Why does Java not support multiple inheritance for classes?

Multiple class inheritance creates the "diamond problem" — if class C extends A and B, and both A and B define the same method, which one does C inherit? Java avoids this by allowing only single class inheritance. Multiple interface inheritance is allowed because interfaces (before default methods) had no implementation to conflict.

🚨 Common Production Issues

Deep inheritance hierarchies — more than 2-3 levels deep becomes hard to understand and maintain. Prefer composition.

Calling overridable methods in constructors — if a parent constructor calls a method overridden by a subclass, the subclass method runs before the subclass constructor finishes, potentially accessing uninitialized fields.

Breaking Liskov Substitution Principle — a subclass that throws exceptions or weakens behavior where the parent did not violates LSP and breaks polymorphic code.

✅ Best Practices

Prefer composition over inheritance — use inheritance only for clear IS-A relationships.

Always annotate overriding methods with @Override — the compiler catches mistakes.

Never call overridable methods from constructors.

Design for inheritance or prohibit it — mark classes final if not intended for subclassing.

Keep inheritance hierarchies shallow — 2 levels max in most cases.

Use abstract classes to define template methods; use interfaces to define contracts.

🏛️ Architect's Note

Senior Architect's Perspective

In modern Spring Boot applications, inheritance is used sparingly: @MappedSuperclass for audit fields, abstract base controllers for shared error handling, and occasionally for strategy patterns. The dominant pattern is composition via dependency injection — services depend on interfaces, not concrete classes. This is more testable, more flexible, and avoids inheritance pitfalls.

💬 Frequently Asked Questions

What is the Liskov Substitution Principle?

LSP states that objects of a subclass must be substitutable for objects of the parent class without breaking the program. If you have code that works with Payment, it should work with CreditCardPayment and BankTransferPayment without knowing which one it has. Violations: subclass throws exceptions the parent does not, subclass weakens preconditions, or subclass strengthens postconditions.

What are default methods in interfaces and how do they relate to inheritance?

Java 8+ interfaces can have default methods — concrete method implementations. This allows adding methods to interfaces without breaking existing implementations. When a class implements multiple interfaces with conflicting default methods, the class must override the method to resolve the conflict. This is "multiple inheritance of behavior" without the diamond problem.

🎯 Key Takeaways

1extends for class inheritance (single); implements for interfaces (multiple)
2Use super() to call parent constructor; super.method() to call parent method
3Always use @Override annotation when overriding methods
4Prefer composition over inheritance — use inheritance only for IS-A relationships
5Never call overridable methods from constructors
6Sealed classes (Java 17+) restrict which classes can extend — great for pattern matching

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Core Java learning path

Browse the full Core Java topic collection and learning order on SplashRide.

Core Java Interview Prep

Practice Core Java interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Java Polymorphism: Overloading, Overriding, and Dynamic Dispatch

Learn Java polymorphism with runnable examples of overriding and overloading, declared versus runtime types, null overloads, static hiding, and interface contracts.

Abstraction

How abstract classes and interfaces define contracts in Java — when to use each, default methods, and how abstraction enables the SOLID principles.