Start Learning
Javaneer
Back to stage
Stage 0·SOLID in Practice

Interface Segregation

No client should depend on methods it doesn't use. Fat interfaces, the empty method smell, and splitting into focused role interfaces.

12 min readIntermediate
On this page

The Interface Segregation Principle is the smallest of the five and the easiest to apply: no client should be forced to depend on methods it doesn't use. Fat interfaces - the ones with fifteen methods where any given caller needs three - couple clients to code they never touch, so an unrelated change ripples where it has no business going. The fix is to split them into small, focused role interfaces.

The fat interface smell

ledger-legacy has a sprawling LedgerService interface that grew to serve everyone:

interface LedgerService {
    void recordTransaction(Transaction t);
    List<Transaction> findByAccount(String id);
    BigDecimal calculateFees(String id);
    String exportCsv(String id);
    String exportPdf(String id);
    void reindexSearch();
    void purgeOldRecords(int days);
    void runNightlyBatch();
}

The reporting UI only needs findByAccount and exportPdf, but it depends on the whole interface - including runNightlyBatch and purgeOldRecords. Consequences:

  • A change to the batch method's signature forces a recompile/redeploy of clients that never call it.
  • Test doubles are painful: to mock LedgerService for a report test, you must stub eight methods you don't care about.
  • The interface lies about what each client actually needs, obscuring the real dependencies.

The empty-implementation tell

The clearest sign of an ISP violation is an implementation forced to stub methods it can't meaningfully support:

class ReadOnlyReportView implements LedgerService {
    public List<Transaction> findByAccount(String id) { ... }   // the one it needs
    public void recordTransaction(Transaction t) {
        throw new UnsupportedOperationException();   // 🚩 forced to implement, can't support
    }
    public void runNightlyBatch() { /* do nothing */ }   // 🚩 meaningless here
    // ...six more stubs...
}

Those UnsupportedOperationExceptions and empty bodies are the interface telling you it bundles unrelated roles - and note they're also LSP violations, since callers can't safely use those methods. Fat interfaces breed Liskov problems.

Splitting into role interfaces

Separate the interface along the roles clients actually play:

interface TransactionRecorder { void recordTransaction(Transaction t); }
interface TransactionReader   { List<Transaction> findByAccount(String id); }
interface LedgerExporter      { String exportCsv(String id); String exportPdf(String id); }
interface LedgerMaintenance   { void reindexSearch(); void purgeOldRecords(int days); void runNightlyBatch(); }

Now the reporting UI depends on just TransactionReader + LedgerExporter - and nothing else can break it. A class can still implement several of these if it genuinely fills those roles (a full service might implement all four), but clients depend only on the narrow role they use. Interfaces are defined by the consumer's needs, not the implementer's convenience.

ISP is SRP for interfaces

Notice the symmetry: SRP says a class should have one reason to change; ISP says an interface should serve one kind of client. A fat interface has many reasons to change (every client's needs), so segregating it is applying the single-responsibility idea to the contract, not the implementation. Small interfaces also make Dependency Inversion (next lesson) cleaner - you invert against a precise role, not a kitchen sink.

One universal remote vs. labeled switches

A fat interface is a universal remote with 60 buttons handed to every family member - the person who just wants to turn on the lamp must hold a device covered in buttons for the projector, the blinds, and the sound system, and if the projector firmware changes, the whole remote gets recalled. ISP is replacing it with simple labeled switches: the lamp switch does one thing, and whoever only needs light isn't affected when the projector is swapped. People depend on the switch for their job, not on a monolith of everyone's controls.

Segregate the interface

A Machine interface has print(doc), scan(doc), fax(doc), and staple(doc). A simple office printer implements only print - so its class throws UnsupportedOperationException for the other three. A high-end multifunction device implements all four. Show how ISP fixes the simple printer's problem, and explain why a class implementing multiple small interfaces is fine.

What problem does the Interface Segregation Principle solve?

Key takeaways

  • ISP: no client should be forced to depend on methods it doesn't use - keep interfaces small and role-focused.
  • The tell of a fat interface is implementations stubbing methods with UnsupportedOperationException or empty bodies (also an LSP smell).
  • Fat interfaces couple every client to every method, so an unrelated change forces recompiles and makes test doubles painful.
  • Split by the roles clients actually play; a class may implement several small interfaces if it genuinely fills those roles.
  • Define interfaces by the consumer's needs, not the implementer's convenience - ISP is essentially SRP applied to contracts.
Was this lesson helpful?
Edit this page on GitHub