Fundamentals 12 min read

Why Prefer Composition Over Inheritance and How to Apply It in Java

The article explains the drawbacks of deep inheritance hierarchies in object‑oriented design, illustrates the problem with a bird example, and demonstrates how composition, interfaces, delegation, and Java 8 default methods can replace inheritance to improve code readability, maintainability, and flexibility.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why Prefer Composition Over Inheritance and How to Apply It in Java

In object‑oriented programming a classic design principle is "composition over inheritance", and the Alibaba Java Development Manual also advises using inheritance cautiously and preferring composition.

Why Inheritance Is Discouraged

Developers often start with inheritance for code reuse, but deep or complex inheritance hierarchies hurt maintainability. The article uses a bird hierarchy: an abstract class AbstractBird defines a fly() method, while subclasses like Ostrich override it to throw an exception because ostriches cannot fly.

public class AbstractBird {
    // ... other members ...
    public void fly() { /* ... */ }
}

public class Ostrich extends AbstractBird {
    @Override
    public void fly() {
        throw new UnSupportedMethodException("I can't fly.");
    }
}

This approach solves the immediate problem but is not elegant; every non‑flying bird (e.g., penguin) must repeat the same override, leading to code duplication.

To avoid this, the hierarchy can be split into AbstractFlyableBird and AbstractUnFlyableBird , but adding more behaviors (tweeting, laying eggs) quickly deepens the inheritance tree, making the code hard to read and tightly coupled.

Advantages of Composition

Composition, interfaces, and delegation can replace inheritance. Define behavior interfaces:

public interface Flyable { void fly(); }
public interface Tweetable { void tweet(); }
public interface EggLayable { void layEgg(); }

Implement these interfaces in separate ability classes and compose them in concrete bird classes, delegating calls to the ability objects.

public class Ostrich implements Tweetable, EggLayable {
    private TweetAbility tweetAbility = new TweetAbility();
    private EggLayAbility eggLayAbility = new EggLayAbility();
    @Override public void tweet() { tweetAbility.tweet(); }
    @Override public void layEgg() { eggLayAbility.layEgg(); }
}

Java 8 default methods can also provide a default implementation directly in interfaces, reducing the need for separate ability classes.

public interface Flyable {
    default void fly() { /* default implementation */ }
}

When to Use Inheritance vs. Composition

If a hierarchy is shallow, stable, and primarily expresses an "is‑a" relationship, inheritance is acceptable. When the hierarchy becomes deep, unstable, or the code needs frequent extension, composition should be preferred to keep coupling low.

Some design patterns inherently use composition (Decorator, Strategy, Composite) while others rely on inheritance (Template). In rare cases where a third‑party class cannot be modified (e.g., extending FeignClient to override encode() ), inheritance may be unavoidable.

Overall, the article argues that, except for specific scenarios, composition, interfaces, and delegation can fully replace inheritance, leading to cleaner and more maintainable Java code.

Design PatternsJavaSoftware Architectureobject-orientedinheritancecomposition
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.