You are currently viewing Liskov Substitution Principle In Java

Liskov Substitution Principle In Java

Introduction

The Liskov Substitution Principle (LSP) is the “L” in the SOLID principles and a cornerstone of robust object-oriented design.

Definition: Subtypes must be substitutable for their base types.

This means that objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program.

Violations of LSP lead to broken inheritance hierarchies and brittle code. In this article, we’ll explore how to apply LSP correctly using real-world Java examples.


1. The Basic Idea

Consider this example:

class Bird {
    void fly() {
        System.out.println("Flying");
    }
}

class Sparrow extends Bird { }

class Ostrich extends Bird { }

If we write:

Bird bird = new Ostrich();
bird.fly(); // ❌ Problem: ostriches can't fly

We’re violating the Liskov Substitution Principle because substituting Ostrich breaks the expected behavior.


2. Fixing the Hierarchy

We can restructure our design:

interface Bird { }

interface FlyingBird extends Bird {
    void fly();
}

class Sparrow implements FlyingBird {
    public void fly() {
        System.out.println("Flying");
    }
}

class Ostrich implements Bird {
    // No fly() method
}

Now, we can use FlyingBird where flying behavior is expected, and Bird when it’s not assumed.


3. Real-World Example: Document Exporters

Suppose we build a document exporting tool:

abstract class DocumentExporter {
    abstract void export(String content);
}

Now we create:

class PdfExporter extends DocumentExporter {
    public void export(String content) {
        System.out.println("Exporting to PDF: " + content);
    }
}

class ReadOnlyExporter extends DocumentExporter {
    public void export(String content) {
        throw new UnsupportedOperationException("Read-only documents can't be exported.");
    }
}

This violates LSP: calling export() on ReadOnlyExporter breaks the expected contract.


4. Correcting the Design

Refactor with a clearer interface contract:

interface Exportable {
    void export(String content);
}

class PdfExporter implements Exportable {
    public void export(String content) {
        System.out.println("Exporting to PDF: " + content);
    }
}

class ReadOnlyDocument {
    // No export functionality
}

Now we’ve ensured that Exportable truly means “can be exported,” without tricking consumers into catching exceptions.


5. When You See LSP Violations

  • Subclasses throw exceptions in overridden methods
  • Subclass behavior contradicts superclass expectations
  • Tests break when substituting child for parent

Conclusion

Following the Liskov Substitution Principle ensures your inheritance structures are logical, safe, and predictable. It’s not just about compiling — it’s about trust between classes.

You can find the complete code of this article here in GitHub.

📚 Related: Interface Segregation Principle in Java

Noel Kamphoa

Experienced software engineer with expertise in Telecom, Payroll, and Banking. Now Senior Software Engineer at Societe Generale Paris.