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."




