Skip to main content

Command Palette

Search for a command to run...

Liskov Substitution Principle (LSP) in Java

Updated
β€’5 min readβ€’View as Markdown
Liskov Substitution Principle (LSP) in Java

Introduction

When designing software using inheritance, a common mistake developers make is creating subclasses that change expected behavior.

This leads to:

Unexpected bugs
Broken polymorphism
Difficult maintenance

The Liskov Substitution Principle (LSP) helps prevent this.

It is the third principle in the SOLID design principles, introduced by Barbara Liskov.


Definition of LSP

Liskov Substitution Principle states:

Objects of a superclass should be replaceable with objects of its subclass without affecting program correctness.


Simple Understanding

If:

Parent obj = new Child();

Then:

πŸ‘‰ The program should still behave correctly

πŸ‘‰ The subclass must not break expected behavior

That is the core idea of LSP.


🧩 Why LSP Matters in Java

Java heavily uses:

  • Inheritance

  • Polymorphism

  • Method overriding

If subclasses behave differently than expected, it leads to:

🚩 Runtime errors
🚩 Logical bugs
🚩 Hard-to-debug systems

LSP ensures:

βœ” Safe inheritance
βœ” Reliable polymorphism
βœ” Maintainable code


Example β€” Violating LSP (Rectangle–Square Problem)

This is the most famous example of LSP violation.

Step 1 β€” Create Rectangle Class

class Rectangle {

    protected int width;
    protected int height;

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

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

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

Step 2 β€” Create Square Class (Wrong Design)

class Square extends Rectangle {

    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // forces square behavior
    }

    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height; // forces square behavior
    }
}

Step 3 β€” Test Code

public class Main {

    public static void main(String[] args) {

        Rectangle rectangle = new Square();

        rectangle.setWidth(5);
        rectangle.setHeight(10);

        System.out.println(rectangle.getArea());
    }
}

Expected vs Actual Output

Expected:

50

Actual:

100

🚨 Why This Violates LSP

Because:

  • Rectangle expects width and height to be independent

  • Square forces width and height to be equal

So:

πŸ‘‰ Square changes behavior

πŸ‘‰ Rectangle cannot be safely replaced

πŸ‘‰ LSP is violated


🧩 Problem Flow (Violation Case)

Rectangle expected behavior
        ↓
Square modifies behavior
        ↓
Rectangle replaced by Square
        ↓
Unexpected result occurs
        ↓
Program correctness breaks 

Correct Design β€” Following LSP

Instead of forcing inheritance, we use abstraction.


Step 1 β€” Create Abstract Shape

abstract class Shape {

    abstract int getArea();
}

Step 2 β€” Create Rectangle Class

class Rectangle extends Shape {

    private int width;
    private int height;

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

    @Override
    int getArea() {
        return width * height;
    }
}

Step 3 β€” Create Square Class

class Square extends Shape {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    @Override
    int getArea() {
        return side * side;
    }
}

Step 4 β€” Test Code

public class Main {

    public static void main(String[] args) {

        Shape rectangle = new Rectangle(5, 10);
        Shape square = new Square(5);

        System.out.println(rectangle.getArea());
        System.out.println(square.getArea());
    }
}

Output

50
25

Now:

βœ” No behavior change

βœ” Safe substitution

βœ” LSP followed


🧠 LSP Design Flow (Correct Case)

Create Parent Abstraction
            ↓
Create Independent Child Classes
            ↓
Override Behavior Safely
            ↓
Replace Parent with Child
            ↓
Program works correctly βœ”

🧩 Real-World Example β€” Bird System

Let's look at a realistic case.


Bad Design (Violates LSP)

class Bird {

    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Penguin extends Bird {

    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins cannot fly");
    }
}

Problem:

Bird bird = new Penguin();
bird.fly(); // Runtime error

Better Design (Follows LSP)

Separate behavior properly.

abstract class Bird {

    abstract void move();
}

class FlyingBird extends Bird {

    @Override
    void move() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {

    @Override
    void move() {
        System.out.println("Swimming");
    }
}

Now:

βœ” All birds move

βœ” Behavior remains valid

βœ” No broken expectations


🧠 Key Rules of LSP

Follow these rules while designing subclasses:


1️⃣ Do Not Change Expected Behavior

Subclass must behave like parent.

Bad:

throw new UnsupportedOperationException();

2️⃣ Do Not Strengthen Preconditions

Subclass should not require stricter input rules.


3️⃣ Do Not Weaken Postconditions

Subclass must return valid results.


4️⃣ Preserve Parent Contracts

Follow the parent class expectations.


⚠️ Signs You're Violating LSP

Watch for:

🚩 Many instanceof checks

🚩 Overridden methods throwing exceptions

🚩 Unexpected output changes

🚩 Subclass restricting parent behavior

🚩 Breaking polymorphism


Benefits of Following LSP

βœ” Predictable inheritance

βœ” Cleaner architecture

βœ” Fewer runtime errors

βœ” Better extensibility

βœ” Easier maintenance


Summary

The Liskov Substitution Principle (LSP) ensures that:

βœ” Subclasses behave correctly

βœ” Parent objects can be replaced safely

βœ” Polymorphism works properly

βœ” Software becomes maintainable


🧩 One-Line Takeaway

πŸ‘‰ "If a subclass cannot replace its parent without breaking behavior, the design violates LSP."


Design Principles for Java Developers

Part 8 of 10

This series explains core Java design principles and SOLID principles with simple examples, real-world use cases, and interview-focused explanations to help developers write clean, maintainable, and scalable code.

Up next

Interface Segregation Principle (ISP) in Java

SOLID Design Principle "Clients should not be forced to depend on interfaces they do not use." The Interface Segregation Principle (ISP) is one of the five SOLID principles that helps developers des

More from this blog

Booster TechLab

27 posts

Booster TechLab is a developer-focused publication sharing interview-ready and real-world content on programming, frameworks, system design, and modern tech to help developers grow faster.