LSP Violation Example — "Bad Code"
Code Overview
package SOLID.LSP.BadCode;
public class File {
public void read() {
System.out.println("reading from file...");
}
public void write() {
System.out.println("writing a file....");
}
}
public class ReadOnlyFile extends File {
@Override
public void write() {
// The parent promised writing, but this subtype takes it away.
throw new UnsupportedOperationException("Can't write to a read-only file");
}
}
public class Main {
public static void main(String[] args) {
File file = new ReadOnlyFile();
file.read(); // works fine
file.write(); // throwing an exception, violation of LSP.
}
}The comment inside ReadOnlyFile is reworded for teaching. The repo file has a shorter one.
The assignment File file = new ReadOnlyFile() is legal Java. It only shows that the types fit together. It does not show that ReadOnlyFile behaves like a File.
Why This Code is Bad
Subclasses should be replaceable for their parent classes without changing the expected behavior of the program.
Follow the broken promise step by step:
Fileexposeswrite(). A caller receiving aFileis allowed to call it.- The promise (postcondition) is that the write completes. Nothing in
Filesays writing is optional. ReadOnlyFileinherits that public promise.- Its override throws a runtime exception the parent never threw, instead of writing.
- Substituting
ReadOnlyFiletherefore breaks code that worked with an ordinaryFile.
Java checks the method signature, not this promise. Both methods return void and take no arguments, so the compiler accepts the override. LSP covers the behavior the compiler cannot check.
What’s Wrong Conceptually
ReadOnlyFiledoes not behave like theFiledescribed by the parent API.write()changes from a supported operation into an unsupported one.- The subtype adds a surprise unchecked exception (one the compiler does not force callers to handle).
- Callers start adding checks such as
if (!(file instanceof ReadOnlyFile)). Now every caller must know aboutReadOnlyFile, which defeats the point of substitution.
The deeper problem is the parent class: it bundles reading and writing, but some files can only be read. The GoodCode version separates those capabilities so impossible calls fail at compile time.
See GoodCode for the capability-based design and LSP-Notes.md for contracts, Rectangle/Square, payment examples, and trade-offs.