Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use.
UnsupportedOperationException for half the API.Why It Matters
- Honest implementations — No dummy
eat()on a robot — or throwingUnsupportedOperationException. - Stable contracts — Changing
Feedabledoes not force recompile of everyWorkableonly. - Clearer tests — Mock only the small interface the class under test needs.
In Layman's Terms
A restaurant menu with one item "Full course + drink + dessert" forces every customer to order it. Better: à la carte — pick only the dishes you want.
ISP is à la carte interfaces: Prints, Scans, Faxes instead of one Machine that does everything and forces a simple printer to fake fax().
Real-World Analogy
🔧 USB-C vs kitchen gadget
A USB-C port advertises what it actually supports (power, data, video) — devices negotiate capabilities instead of pretending every socket does everything.
Your Java interfaces should do the same: capability-based, not kitchen-sink.
Visual
interface Worker {
void work();
void eatLunch();
void attendStandup();
}
// Robot implements Worker → eatLunch() ??? interface Workable { void work(); }
interface Feedable { void eatLunch(); }
interface TeamMember { void attendStandup(); }
class Robot implements Workable { ... }
class Human implements Workable, Feedable, TeamMember { ... }Fat Worker forces irrelevant methods. Segregated roles let Robot implement only Workable.
When to Use
- An interface has 10+ methods and typical clients use 3.
- Implementors regularly leave methods as `throw new UnsupportedOperationException()`.
- You see `@Override void foo() { }` empty bodies — sign the interface is too broad.
- Testing forces you to mock a giant dependency for a tiny interaction.
When NOT to Use
- The interface is already cohesive — every implementor uses every method.
- Splitting would create dozens of tiny interfaces that always travel together — consider one facade instead.
Anti-Pattern (What NOT to Do)
DocumentStore — not every backend can do everythingBadjavapublic interface DocumentStore {
byte[] read(String id);
void write(String id, byte[] data);
void replicateToCluster(); // S3? No-op or throw
void applyTransactionalBatch(); // Redis alone? Awkward
Stream<String> sqlQuery(String sql); // Key-value store — meaningless
}
public class S3DocumentStore implements DocumentStore {
public byte[] read(String id) { /* ... */ }
public void write(String id, byte[] data) { /* ... */ }
public void replicateToCluster() {
throw new UnsupportedOperationException(); // ISP violation smell
}
// ...
}DocumentStore cannot assume every method works — the abstraction leaks.The Right Way
public interface Readable {
byte[] read(String id);
}
public interface Writable {
void write(String id, byte[] data);
}
public interface Replicating {
void replicateToCluster();
}
public class S3Store implements Readable, Writable {
/* only what S3 actually does */
}
public class ClusteredStore implements Readable, Writable, Replicating {
/* full stack */
}Readable only when they only read — no fake methods.Pros & Cons
- Smaller, clearer contracts — easier to implement and reason about.
- Works well with composition:
new Service(repo)whererepois onlyReadable. - Reduces coupling between unrelated features sharing one fat type.
- More interface names to navigate — needs good naming.
- Risk of explosion of one-method interfaces — balance with cohesion.
Fat interface vs segregated interfaces
| Aspect | Monolithic interface | Segregated interfaces |
|---|---|---|
| Implementor burden | Implement everything or throw | Implement only relevant roles |
| Client coupling | Depends on full surface area | Depends on minimal role |
| Change ripple | Any method change affects all users | Changes localized to role |
| Typical smell | UnsupportedOperationException | Small focused types |
Common Confusions Cleared Up
SRP is one reason to change per class. ISP is one focused role per interface so clients do not depend on unused fat APIs. Both fight bloat at different granularities.
If you force subclasses to implement useless methods that throw, you often break LSP (substitutability). ISP fixes the root cause by narrowing the interface so empty or throwing implementations disappear.
Common Mistakes / Anti-Patterns
- God interface inherited by every DAO regardless of datastore.
- Using default methods in Java to paper over missing implementations instead of splitting.
- Copy-pasting the same "not supported" override across subclasses.
Interview Q&A
Likely Follow-up Questions
- When would you merge small interfaces back into one?
- How does Spring Data
Repositoryhierarchy illustrate ISP? - Difference between ISP and Role Interface pattern?
Quick Cheat Sheet
UnsupportedOperationException, empty @Override bodies.