SOLID Principles in Java: Building Resilient Software
The Code Quality Standard
SOLID is the definitive guide for writing maintainable software. For a Java developer, these principles are the difference between a “Legacy Nightmare” and a “Clean Architecture.” Understanding these is essential for building scalable Spring Boot or enterprise Jakarta EE applications.
The Five Principles
1. S: Single Responsibility Principle (SRP)
A class should have one, and only one, reason to change.
- Example: Don’t put SQL logic inside your Domain Entity. Separate business rules from persistence logic.
2. O: Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
- Example: Use interfaces and polymorphism to add new payment methods (e.g.,
PayPalPayment,StripePayment) without editing the mainPaymentProcessorcode.
3. L: Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without breaking the application.
- Example: If
SquareextendsRectangle, but changing the width of aSquarealso changes its height, it violates LSP because aRectangleis expected to have independent dimensions.
4. I: Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they do not use.
- Example: Break a large
Workerinterface intoWorkableandFeedableso aRobotdoesn’t have to implement aneat()method.
5. D: Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Example: Inject an
EmailProviderinterface into your service instead of hardcoding a concreteGmailServiceclass.
Practice Exercise: Refactoring a “God Class”
We will refactor an OrderProcessor that handles validation, persistence, and notifications (violating SRP).
The Problem (Violation of SRP)
public class OrderProcessor {
public void process(Order order) {
if (order.items().isEmpty()) throw new IllegalArgumentException("No items");
// Direct DB dependency (Low-level)
Database.save(order);
// Direct Email dependency (Low-level)
EmailService.send("Order processed!");
}
}
The Optimized Solution (SRP + DIP)
public class OrderProcessor {
private final OrderRepository repository;
private final NotificationService notification;
public OrderProcessor(OrderRepository repository, NotificationService notification) {
this.repository = repository;
this.notification = notification;
}
public void process(Order order) {
// Validation is encapsulated in the Domain model
order.validate();
repository.save(order);
notification.notifyUser(order.getCustomerId(), "Order processed!");
}
}
Why This Works
- Maintainability: If you switch from SQL to MongoDB, you only replace the
OrderRepositoryimplementation. TheOrderProcessorremains untouched. - Testability: You can easily mock
OrderRepositoryandNotificationServiceto unit test the processor’s business logic without needing a real database or mail server. - Flexibility: New notification types (SMS, Push) can be added simply by implementing the
NotificationServiceinterface.
Summary
SOLID principles are not rigid laws, but they are incredibly reliable heuristics. By following them, you create systems that are easy to understand, easy to change, and easy to test—the three hallmarks of professional software engineering.