Interface Segregation Principle in Java: Many Small Interfaces Over One Large Interface
-
Jason Yang - 12 May, 2026
- Updated 13 May, 2026
- Views —
The Interface Segregation Principle, also known as ISP, is the fourth principle in SOLID.
The definition is:
A class should not be forced to depend on methods it does not use.
A simpler way to say it is:
Prefer many small, focused interfaces over one large general-purpose interface.
This principle is very practical in Java.
When an interface becomes too large, classes that implement it may be forced to provide methods they do not actually need. This often leads to empty methods, fake implementations, or UnsupportedOperationException.
Let’s look at a simple example.
A Bad Example: One Large SmartPhone Interface
Imagine we are designing a phone system.
At first, we create one interface called SmartPhone.
public interface SmartPhone {
void call();
void useInternet();
void wirelessCharge();
}
This interface has three methods:
call()
useInternet()
wirelessCharge()
For a modern phone, this may be fine.
public class GalaxyPhone implements SmartPhone {
@Override
public void call() {
System.out.println("Calling...");
}
@Override
public void useInternet() {
System.out.println("Using internet...");
}
@Override
public void wirelessCharge() {
System.out.println("Wireless charging...");
}
}
So far, there is no problem.
But what about an older phone?
Let’s say the old phone can make calls and use the internet, but it does not support wireless charging.
Still, because it implements SmartPhone, it must implement all methods.
public class OldPhone implements SmartPhone {
@Override
public void call() {
System.out.println("Calling...");
}
@Override
public void useInternet() {
System.out.println("Using internet...");
}
@Override
public void wirelessCharge() {
throw new UnsupportedOperationException("Wireless charging is not supported.");
}
}
This is a design smell.
OldPhone does not support wireless charging, but the interface forces it to implement wirelessCharge().
That is an ISP violation.
What Is the Problem?
The problem is not that OldPhone is wrong.
The problem is that the SmartPhone interface is too large.
It assumes that every phone has all three features:
Calling
Internet
Wireless charging
But that assumption is not always true.
As a result, some classes are forced to implement methods they do not use.
This often creates code like this:
throw new UnsupportedOperationException();
or this:
@Override
public void wirelessCharge() {
// do nothing
}
Both are signs that the interface may be too broad.
Why Large Interfaces Become Painful
Large interfaces can look convenient at first.
We may think:
Let’s put all related methods into one interface.
But as the system grows, that interface becomes harder to use.
The problem is that one interface starts representing too many different roles.
For example, this interface:
public interface SmartPhone {
void call();
void useInternet();
void wirelessCharge();
}
actually mixes three different capabilities:
Callable
Internet usable
Wireless chargeable
Not every class needs all of them.
So instead of making one big interface, we can split it into smaller interfaces.
Applying ISP
A better design is to create small, focused interfaces.
public interface Callable {
void call();
}
public interface InternetUsable {
void useInternet();
}
public interface WirelessChargeable {
void wirelessCharge();
}
Now each class can implement only what it actually supports.
A modern phone can implement all three interfaces.
public class GalaxyPhone implements Callable, InternetUsable, WirelessChargeable {
@Override
public void call() {
System.out.println("Calling...");
}
@Override
public void useInternet() {
System.out.println("Using internet...");
}
@Override
public void wirelessCharge() {
System.out.println("Wireless charging...");
}
}
An older phone can implement only the interfaces it needs.
public class OldPhone implements Callable, InternetUsable {
@Override
public void call() {
System.out.println("Calling...");
}
@Override
public void useInternet() {
System.out.println("Using internet...");
}
}
Now OldPhone does not need to implement wirelessCharge().
This is cleaner and safer.
The Client Also Becomes Cleaner
ISP is not only about implementation classes.
It is also about the client code that uses those interfaces.
For example, a wireless charging service should not depend on the full SmartPhone interface.
It only needs something that can be wirelessly charged.
So we can write:
public class ChargingService {
public void chargeWirelessly(WirelessChargeable device) {
device.wirelessCharge();
}
}
Now the method accepts only WirelessChargeable.
Usage:
public class Main {
public static void main(String[] args) {
ChargingService chargingService = new ChargingService();
GalaxyPhone galaxyPhone = new GalaxyPhone();
OldPhone oldPhone = new OldPhone();
chargingService.chargeWirelessly(galaxyPhone);
// This does not compile:
// chargingService.chargeWirelessly(oldPhone);
}
}
This is a good thing.
The compiler prevents us from passing OldPhone to a service that requires wireless charging.
Instead of failing at runtime, the problem is caught at compile time.
A More Practical Example: Printer, Scanner, and Fax
The phone example is easy to understand, but let’s look at a more common software design example.
Imagine we are designing a machine interface.
public interface Machine {
void print();
void scan();
void fax();
}
This interface works for a multifunction printer.
public class MultiFunctionPrinter implements Machine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
System.out.println("Scanning...");
}
@Override
public void fax() {
System.out.println("Sending fax...");
}
}
But what about a simple printer?
A simple printer can only print.
It cannot scan or fax.
Still, it is forced to implement all methods.
public class SimplePrinter implements Machine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
throw new UnsupportedOperationException("Scan is not supported.");
}
@Override
public void fax() {
throw new UnsupportedOperationException("Fax is not supported.");
}
}
This is another ISP violation.
The Machine interface is too large.
It forces SimplePrinter to depend on methods it does not use.
A Better Printer Design
A better design is to split the interface by capability.
public interface Printable {
void print();
}
public interface Scannable {
void scan();
}
public interface Faxable {
void fax();
}
Now a simple printer only implements Printable.
public class SimplePrinter implements Printable {
@Override
public void print() {
System.out.println("Printing...");
}
}
A multifunction printer can implement all three.
public class MultiFunctionPrinter implements Printable, Scannable, Faxable {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
System.out.println("Scanning...");
}
@Override
public void fax() {
System.out.println("Sending fax...");
}
}
Now each class only implements the behavior it actually supports.
This is the core idea of ISP.
Services Should Depend on the Smallest Interface They Need
Another important part of ISP is how services depend on interfaces.
For example, a print service only needs printing behavior.
So it should depend on Printable.
public class PrintService {
public void printDocument(Printable printer) {
printer.print();
}
}
A scan service only needs scanning behavior.
public class ScanService {
public void scanDocument(Scannable scanner) {
scanner.scan();
}
}
A fax service only needs fax behavior.
public class FaxService {
public void sendFax(Faxable faxMachine) {
faxMachine.fax();
}
}
This is better than making every service depend on one large Machine interface.
Before:
PrintService → Machine
ScanService → Machine
FaxService → Machine
After:
PrintService → Printable
ScanService → Scannable
FaxService → Faxable
Each service now depends only on what it actually uses.
That makes the design easier to understand and easier to change.
A Backend Example: User Operations
Let’s look at a backend-style example.
Suppose we create one large user service interface.
public interface UserOperations {
User findById(Long id);
void createUser(User user);
void updateUser(User user);
void deleteUser(Long id);
void exportUsersToCsv();
void sendWelcomeEmail(User user);
}
This interface mixes many responsibilities.
It includes:
Querying users
Creating users
Updating users
Deleting users
Exporting users
Sending emails
Now imagine we have a class that only needs to read user data.
For example:
public class UserProfileService {
private final UserOperations userOperations;
public UserProfileService(UserOperations userOperations) {
this.userOperations = userOperations;
}
public User getProfile(Long userId) {
return userOperations.findById(userId);
}
}
UserProfileService only uses findById().
But because it depends on UserOperations, it is also indirectly coupled to methods like:
createUser()
deleteUser()
exportUsersToCsv()
sendWelcomeEmail()
This is unnecessary.
The client is depending on methods it does not use.
A Better Backend Design
We can split the large interface into smaller ones.
public interface UserReader {
User findById(Long id);
}
public interface UserWriter {
void createUser(User user);
void updateUser(User user);
void deleteUser(Long id);
}
public interface UserExporter {
void exportUsersToCsv();
}
public interface UserNotifier {
void sendWelcomeEmail(User user);
}
Now UserProfileService can depend only on UserReader.
public class UserProfileService {
private final UserReader userReader;
public UserProfileService(UserReader userReader) {
this.userReader = userReader;
}
public User getProfile(Long userId) {
return userReader.findById(userId);
}
}
This is much cleaner.
UserProfileService does not need to know anything about creating users, deleting users, exporting CSV files, or sending emails.
It only depends on the behavior it actually needs.
This is ISP in a backend service design.
ISP and Spring Boot
In Spring Boot, we often use interfaces for services, repositories, external clients, and strategies.
ISP is useful when those interfaces start becoming too large.
For example, instead of one huge external integration interface:
public interface ErpClient {
void createInvoice();
void cancelInvoice();
void syncInventory();
void createPurchaseOrder();
void sendPaymentRequest();
void exportReport();
}
we may split it by business capability:
public interface InvoiceClient {
void createInvoice();
void cancelInvoice();
}
public interface InventoryClient {
void syncInventory();
}
public interface PurchaseOrderClient {
void createPurchaseOrder();
}
public interface PaymentClient {
void sendPaymentRequest();
}
Then each service depends only on the client interface it needs.
For example:
@Service
public class InvoiceService {
private final InvoiceClient invoiceClient;
public InvoiceService(InvoiceClient invoiceClient) {
this.invoiceClient = invoiceClient;
}
public void createInvoice() {
invoiceClient.createInvoice();
}
}
This design is easier to test because InvoiceService only needs a mock of InvoiceClient, not a huge ErpClient with many unrelated methods.
ISP Does Not Mean Every Interface Must Have One Method
A common misunderstanding is thinking that ISP means every interface should have only one method.
That is not true.
An interface can have multiple methods if they belong to the same role.
For example:
public interface InvoiceClient {
void createInvoice();
void cancelInvoice();
InvoiceStatus getInvoiceStatus(Long invoiceId);
}
This can still follow ISP because all methods are related to invoice operations.
The key question is not:
How many methods does this interface have?
The better question is:
Do all clients that use this interface actually need these methods?
If the answer is no, the interface may need to be split.
Common Signs of ISP Violations
In real projects, ISP violations often appear in these forms:
@Override
public void someMethod() {
throw new UnsupportedOperationException();
}
or:
@Override
public void someMethod() {
// not used
}
or comments like:
// This method is only used by AdminService
Another warning sign is when an interface has methods from different business areas.
For example:
User lookup
User creation
Email notification
CSV export
Audit logging
If all of these are in one interface, the interface may be too broad.
A good interface should represent a clear role.
ISP and SRP
ISP is closely related to SRP.
SRP focuses on class responsibilities.
ISP focuses on interface responsibilities.
If a class has too many responsibilities, it may violate SRP.
If an interface has too many unrelated methods, it may violate ISP.
For example:
SRP question:
Does this class have too many reasons to change?
ISP question:
Does this interface force clients to depend on methods they do not use?
Both principles push us toward smaller, clearer, more focused designs.
ISP and LSP
ISP is also related to LSP.
When an interface is too large, some classes may be forced to implement methods they cannot really support.
That often leads to this:
throw new UnsupportedOperationException();
This can also create an LSP problem.
For example, if SimplePrinter implements Machine, callers may expect it to support scan() and fax().
But if it throws an exception, it cannot safely behave like a Machine.
So by applying ISP and splitting the interface, we can also reduce LSP violations.
Before and After
Before ISP:
public interface Machine {
void print();
void scan();
void fax();
}
public class SimplePrinter implements Machine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
throw new UnsupportedOperationException("Scan is not supported.");
}
@Override
public void fax() {
throw new UnsupportedOperationException("Fax is not supported.");
}
}
Problem:
SimplePrinter is forced to implement methods it does not support.
After ISP:
public interface Printable {
void print();
}
public interface Scannable {
void scan();
}
public interface Faxable {
void fax();
}
public class SimplePrinter implements Printable {
@Override
public void print() {
System.out.println("Printing...");
}
}
Better design:
SimplePrinter only implements the behavior it actually supports.
A Practical Way to Think About ISP
When I think about ISP, I usually ask these questions:
Is this interface too broad?
Are clients using only one or two methods from this interface?
Are some implementations throwing UnsupportedOperationException?
Are there empty method implementations?
Can this interface be split by role or capability?
These questions are more useful than memorizing the formal definition.
ISP is not about making the smallest possible interfaces.
It is about making interfaces that match real client needs.
Summary
The Interface Segregation Principle says that a class should not be forced to depend on methods it does not use.
In practice, this means we should avoid large general-purpose interfaces and prefer smaller, role-based interfaces.
In the phone example, instead of one large SmartPhone interface with call(), useInternet(), and wirelessCharge(), we can split it into Callable, InternetUsable, and WirelessChargeable.
In the printer example, instead of one large Machine interface with print(), scan(), and fax(), we can split it into Printable, Scannable, and Faxable.
This makes the code cleaner, safer, and easier to test.
The key question is:
Does this client really need all the methods in this interface?
If the answer is no, the interface may be too large.