1. Imagine You Work in a Restaurant 🍽️
Hey, developers! Today, let’s learn another important SOLID principle in Java: the Interface Segregation Principle (ISP).
Don’t worry about the technical name. Let’s understand it with a simple example.
Imagine you work in a restaurant.
There are three types of employees:
- Chef
- Waiter
- Kitchen maintenance staff
Each employee has different responsibilities.
The chef prepares food and decides the menu.
The waiter takes orders and serves food.
The maintenance staff cleans the kitchen and restocks groceries.
Now imagine the restaurant manager gives every employee the same list of tasks.
Every employee must:
1. Prepare food
2. Decide the menu
3. Take orders
4. Serve food
5. Clean the kitchen
6. Restock groceries
Does this make sense?
Of course not!
A waiter shouldn’t be forced to prepare food or clean the kitchen if those tasks aren’t part of the waiter’s responsibilities.
Similarly, in Java, a class shouldn’t be forced to implement methods that it doesn’t need.
This is the basic idea behind the Interface Segregation Principle.
2. What Is the Interface Segregation Principle?
The Interface Segregation Principle states:
Clients should not be forced to depend on interfaces they don’t use.
In simple words, instead of creating one large interface containing many unrelated methods, create smaller, focused interfaces.
For example, instead of creating one interface called RestaurantEmployee containing every restaurant task, we can create separate interfaces for chefs, waiters, and maintenance staff.
This makes our code cleaner and easier to maintain.
3. Example That Violates ISP
Let’s create one large interface for all restaurant employees.
Step 1: Create a large interface
public interface RestaurantEmployee {
void prepareFood();
void decideMenu();
void serveFoodAndDrinks();
void takeOrder();
void cleanTheKitchen();
}
This interface contains five different responsibilities.
Now let’s implement it in the Waiter class.
Step 2: Create the Waiter class
public class Waiter implements RestaurantEmployee {
@Override
public void takeOrder() {
System.out.println("Taking order...");
}
@Override
public void serveFoodAndDrinks() {
System.out.println("Serving food and drinks...");
}
@Override
public void prepareFood() {
throw new AssertionError(
"Waiter cannot prepare food!"
);
}
@Override
public void decideMenu() {
throw new AssertionError(
"Waiter cannot decide the menu!"
);
}
@Override
public void cleanTheKitchen() {
throw new AssertionError(
"Waiter cannot clean the kitchen!"
);
}
}
Look carefully at this code.
The waiter only needs two methods:
takeOrder()serveFoodAndDrinks()
But Java forces the waiter to implement all five methods because the class implements RestaurantEmployee.
We have added exceptions to methods that the waiter doesn’t support.
This is a poor design.
Step 3: Test the code
public class Main {
public static void main(String[] args) {
Waiter waiter = new Waiter();
waiter.takeOrder();
waiter.serveFoodAndDrinks();
waiter.prepareFood(); // Throws an error
}
}
Output:
Taking order...
Serving food and drinks...
Exception in thread "main"
java.lang.AssertionError: Waiter cannot prepare food!
The program throws an error because the waiter was forced to implement a method that doesn’t make sense for its role.
Problems with this approach
- Classes must implement unnecessary methods.
- Some methods contain empty implementations or throw exceptions.
- The interface becomes large and difficult to maintain.
- Testing becomes more complicated.
- Adding new employee types may introduce more unnecessary methods.
This is an example of violating ISP.
4. How to Follow ISP Using Smaller Interfaces
Now let’s improve our design.
Instead of one large interface, we’ll create three smaller interfaces.
Step 1: Create ChefTasks
public interface ChefTasks {
void prepareFood();
void decideMenu();
}
This interface contains only the responsibilities of a chef.
Step 2: Create WaiterTasks
public interface WaiterTasks {
void serveFoodAndDrinks();
void takeOrder();
}
This interface contains only the responsibilities of a waiter.
Step 3: Create MaintenanceTasks
public interface MaintenanceTasks {
void cleanTheKitchen();
void reStockGroceries();
}
This interface contains only the responsibilities of the maintenance staff.
Now each interface has a clear purpose.
5. Implement the Interfaces
Let’s create the employee classes.
Chef class
public class Chef implements ChefTasks {
@Override
public void prepareFood() {
System.out.println("Preparing food...");
}
@Override
public void decideMenu() {
System.out.println("Deciding menu...");
}
}
The chef only implements the methods defined in ChefTasks.
Waiter class
public class Waiter implements WaiterTasks {
@Override
public void serveFoodAndDrinks() {
System.out.println("Serving food and drinks...");
}
@Override
public void takeOrder() {
System.out.println("Taking order...");
}
}
The waiter implements only the methods it needs.
Maintenance staff class
public class MaintenanceStaff
implements MaintenanceTasks {
@Override
public void cleanTheKitchen() {
System.out.println("Cleaning the kitchen...");
}
@Override
public void reStockGroceries() {
System.out.println("Restocking groceries...");
}
}
The maintenance staff implements only the maintenance responsibilities.
Notice that we no longer need unnecessary methods or exceptions.
6. Test the Improved Design
Let’s create the objects and call their methods.
public class Main {
public static void main(String[] args) {
Chef chef = new Chef();
Waiter waiter = new Waiter();
MaintenanceStaff staff = new MaintenanceStaff();
chef.prepareFood();
chef.decideMenu();
waiter.takeOrder();
waiter.serveFoodAndDrinks();
staff.cleanTheKitchen();
staff.reStockGroceries();
}
}
Output:
Preparing food...
Deciding menu...
Taking order...
Serving food and drinks...
Cleaning the kitchen...
Restocking groceries...
Everything works as expected.
The chef doesn’t need to implement waiter methods.
The waiter doesn’t need to implement chef methods.
The maintenance staff doesn’t need to implement unrelated responsibilities.
This is how we follow the Interface Segregation Principle.
7. Can a Class Implement Multiple Interfaces?
Yes!
Java allows a class to implement multiple interfaces.
Imagine an employee works as both a chef and a waiter.
That employee can implement both interfaces:
public class ChefWaiter
implements ChefTasks, WaiterTasks {
@Override
public void prepareFood() {
System.out.println("Preparing food...");
}
@Override
public void decideMenu() {
System.out.println("Deciding menu...");
}
@Override
public void serveFoodAndDrinks() {
System.out.println("Serving food and drinks...");
}
@Override
public void takeOrder() {
System.out.println("Taking order...");
}
}
This is perfectly valid because the employee genuinely supports all four responsibilities.
The important rule is not that every class must implement exactly one interface.
The rule is that a class should not be forced to depend on functionality it doesn’t need.
8. How Is ISP Used in Spring Boot?
Let’s understand this with a simple Spring Boot example.
Imagine an application sends notifications to users.
It supports email notifications and SMS notifications.
A poorly designed interface might look like this:
public interface NotificationService {
void sendEmail(String message);
void sendSms(String message);
void sendPushNotification(String message);
}
Now imagine an email-only service must implement all three methods.
That could force it to contain unnecessary or unsupported methods.
Instead, we can create focused interfaces.
EmailNotification
public interface EmailNotification {
void sendEmail(String message);
}
SmsNotification
public interface SmsNotification {
void sendSms(String message);
}
Email service implementation
@Service
public class EmailNotificationService
implements EmailNotification {
@Override
public void sendEmail(String message) {
System.out.println(
"Sending email: " + message
);
}
}
SMS service implementation
@Service
public class SmsNotificationService
implements SmsNotification {
@Override
public void sendSms(String message) {
System.out.println(
"Sending SMS: " + message
);
}
}
Now each service implements only the functionality it supports.
This design is easier to maintain and extend.
For example, you can add a new push notification interface without forcing the existing email service to implement a push notification method.
9. Benefits of the Interface Segregation Principle
Following ISP provides several benefits.
1. Cleaner code
Classes contain only the methods relevant to their responsibilities.
2. Better maintainability
Changing one focused interface is less likely to affect unrelated implementations.
3. Easier testing
Classes have fewer irrelevant methods and simpler contracts to test.
4. Better flexibility
Different classes can implement different combinations of interfaces.
5. Reduced coupling
Classes do not need to depend on unrelated functionality.
6. Easier extension
New features can be introduced through focused interfaces without unnecessarily modifying existing classes.
10. How to Identify an ISP Violation
When reviewing Java code, look for these warning signs:
- One interface contains too many unrelated methods.
- Classes implement methods they don’t support.
- Methods are left empty because they aren’t relevant.
- Methods throw exceptions simply because the class doesn’t need that functionality.
- Changes to one responsibility affect many unrelated implementations.
If you find these problems, consider splitting the interface into smaller, cohesive interfaces.
However, don’t create dozens of interfaces without a reason. Group methods according to actual client needs and responsibilities.
11. ISP vs. DIP
Both ISP and DIP are part of the SOLID principles, but they solve different problems.
| Principle | Main idea |
|---|---|
| ISP — Interface Segregation Principle | Keep interfaces small and focused |
| DIP — Dependency Inversion Principle | Make high-level and low-level modules depend on abstractions |
In our restaurant example, ISP tells us not to force a waiter to implement chef methods.
DIP tells us to structure dependencies around abstractions instead of tightly coupling high-level code to concrete implementations.
They complement each other, but they are not the same principle.
12. Final Summary
Let’s revise the concept in simple words.
Imagine a restaurant where every employee is forced to perform every task.
The chef must take orders, the waiter must prepare food, and the maintenance staff must decide the menu.
That design makes no sense.
The same problem occurs in Java when one large interface forces classes to implement methods they don’t need.
Instead, create smaller interfaces based on specific responsibilities.
Remember these points:
- Avoid large interfaces containing unrelated methods.
- Create small, focused interfaces.
- Let classes implement only the interfaces they actually need.
- Avoid empty methods and exceptions used only to satisfy an unnecessarily large interface.
- Design interfaces around the needs of their clients.
The easiest way to remember ISP:
A chef should cook, a waiter should serve, and maintenance staff should maintain.
Similarly, a Java class should implement only the functionality that makes sense for its responsibilities.
Happy coding! 💻