NailYourInterview
SOLID Principles

Liskov Substitution Principle Explained with Java Example

Explore the Liskov Substitution Principle in Java with a hands-on example. See how correct inheritance ensures predictable, bug-free behavior.

Liskov Substitution Principle (LSP)

Video thumbnail

The L in the SOLID principles stands for the Liskov Substitution Principle. By the end of this chapter, you'll be able to confidently answer:

  1. What is the Liskov Substitution Principle?

  2. Why do we need it? (What problems arise if we don’t follow it?)

  3. How do we apply it in real-world code?

What is the Liskov Substitution Principle?

If S is a subclass of T, then objects of type T should be replaceable with objects of type S without breaking the correctness of the program.

I know, this definition might feel a bit confusing at first 😅 — so let’s break it down with a relatable example.

Violation of LSP
Violation of LSP

Let’s say we have a base class Document with methods to open, save, and print the document. We also have some subclasses for specific document types and one of the subclass (TextDocument) violates LSP.

class Document {
    public void open(String filename) {
        System.out.println("Opening the Document");
    }
 
    public void save(String filename) {
        System.out.println("Saving the Document");
    }
 
    public void print(String filename) {
        System.out.println("Printing the Document");
    }
}

What's the problem?

Let’s revisit the definition:

Subclasses should be replaceable for their parent class without breaking the program.

Here, TextDocument is a Document, so replacing a Document reference with a TextDocument object should just work, right?

But it doesn’t. The program crashes at runtime with an exception. That's a violation of LSP.

Changing Behavior Is Also a Violation

Let’s say instead of throwing an exception, TextDocument just logs something:

TextDocument.java
class TextDocument extends Document {
    @Override
    public void open(String filename) {
        System.out.println("Opening the Text Document");
    }
 
    @Override
    public void save(String filename) {
        System.out.println("Saving the Text Document");
    }
 
    public void print(String filename) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
        System.out.println("Method not supported"); // 🤨 Changed behavior
    }
}

Even though it doesn’t crash anymore, it’s still violating LSP. Why?

Because the base class promised actual printing behavior, but now the child class is silently ignoring that and logging instead — the behavior has changed. That's not okay either.

How to Achieve the Liskov Substitution Principle

To fix the earlier issue, we need to redesign the class hierarchy so that only the documents which support printing have the print() method.

Violation of LSP
Structure which follows LSP

Here’s what we’ve done:

  • The base Document class now contains only the common operations: open() and save().

  • We’ve created a new class PrintableDocument that extends Document and adds the print() functionality.

  • Any document type that supports printing — like PDF or Word — will extend PrintableDocument.

  • A document like TextDocument, which doesn’t support printing, will simply extend Document.

This way, we follow the LSP correctly: you can substitute a PrintableDocument wherever printing is expected, and non-printable ones (like TextDocument) don’t pretend to support printing.

class Document {
    private String title;
    private String content;
 
    public void open(String filename) {
        System.out.println("Opening the Document");
    }
 
    public void save(String filename) {
        System.out.println("Saving the Document");
    }
 
    // getters and setters
}

This redesigned class structure keeps our program’s behavior consistent and predictable when using subclass objects in place of their parent class.

On this page