Liskov Substitution in Java
Subtypes must be usable wherever the parent is expected.
Square extends Rectangle can quietly break code that worked perfectly for years.The substitution promise
The Liskov Substitution Principle (LSP): anywhere a parent type is expected, any subtype must work without surprises. Compiling isn't enough — the subtype must keep the parent's behavioral promises.
Rectangle's promise
A Rect promises that setW and setH change the sides independently: set width 5 and height 4, get area 20. A Square must keep its sides equal, so it overrides both setters to set both sides.
class Square extends Rect {
void setW(int v) { w = h = v; }
void setH(int v) { w = h = v; }
}Substitute the square
Code written for Rect receives a Square. What prints?
// Square's setters keep w == h
Rect r = new Square();
r.setW(3);
r.setH(6);
System.out.println(r.w * r.h);18369
Show the answer
36. setW(3) makes a 3×3 square, then setH(6) makes it 6×6. Code that expects Rect's promise (3 × 6 = 18) gets a result the contract never allows — an LSP violation, not just a bug.
Rules of thumb
A subtype may accept more and promise more — never less. Don't strengthen preconditions (rejecting inputs the parent accepted). Don't weaken postconditions (delivering less than promised). Don't throw where the parent wouldn't.
The refusing subclass
Overriding a method just to throw UnsupportedOperationException breaks every caller that trusted the parent's promise. If a subtype can't do what the parent does, it probably shouldn't be a subtype.
// in ReadOnlyList extends MyList:
@Override
void add(String s) {
throw new UnsupportedOperationException();
}Allowed or not?
Which of these break LSP: returning a more specific type from an override, adding a brand-new method, logging before doing the same work, rejecting inputs the parent accepted?
Think about it, then reveal the answer
Only the last one. A covariant return, an extra method or extra logging all keep the parent's promises. Rejecting inputs the parent accepted (a stronger precondition) breaks callers written for the parent.
In real projects
LSP is why you should read the contract, not just the type: List.of(...) returns a List whose add throws, which surprises many developers. Test doubles and plugin implementations must honor their interface's contract too, or they break the code using them.
Key takeaways
- Subtypes must honor the parent's contract
- Don't strengthen preconditions
- Don't weaken postconditions
- Compiling isn't enough; behavior must fit too
Barbara Liskov introduced the principle in a 1987 keynote and received the Turing Award — computing's highest honor — in 2008.
Practice questions
Square extends Rectangle and its setWidth also changes the height. A test sets width 5 and height 4 on a Rectangle and expects area 20, but fails when given a Square. Which principle is broken?
- Liskov Substitution
- Encapsulation
- Single inheritance
- Overloading
Check your answer
Liskov Substitution. Rectangle's contract says width and height change independently. Square can't keep that promise, so it is not a safe substitute.
Rect has setters setW and setH. Square extends Rect and overrides both so they set w and h together. What does this print?
Rect r = new Square();
r.setW(5);
r.setH(4);
System.out.println(r.w * r.h);- 20
- 16
- 25
- 0
Check your answer
16. Square's setters keep both sides equal. setW(5) makes it 5x5, then setH(4) makes it 4x4, so the area is 16, not the 20 Rect promises.