SOLID Principles in Java Explained with Examples | Complete Guide for Beginners & Interviews
SOLID Principles in Java Explained with Real Examples
Writing code that works is only the first step in software development. As applications grow, maintaining, extending, and testing the code becomes more challenging. Poorly designed code often leads to tight coupling, duplicated logic, and bugs that are difficult to fix. This is where the SOLID Principles come into play.
SOLID is a collection of five object-oriented design principles introduced by software engineer Robert C. Martin (Uncle Bob). These principles help developers write clean, maintainable, scalable, and loosely coupled applications. They are widely used in Java development and are commonly discussed during technical interviews.
Whether you are building a small application or a large enterprise system using Spring Boot, following SOLID principles can significantly improve your code quality and make future maintenance easier.
What Does SOLID Stand For?
| Letter | Principle | Purpose |
|---|---|---|
| S | Single Responsibility Principle | A class should have only one reason to change. |
| O | Open/Closed Principle | Software should be open for extension but closed for modification. |
| L | Liskov Substitution Principle | Subclasses should be replaceable with their parent classes. |
| I | Interface Segregation Principle | Clients should not depend on methods they don't use. |
| D | Dependency Inversion Principle | Depend on abstractions instead of concrete implementations. |
Why Are SOLID Principles Important?
Imagine working on an application that has been under development for several years. Hundreds of classes interact with each other, multiple developers contribute to the project, and new features are added every month. Without proper design principles, even a small change can introduce unexpected bugs.
SOLID principles help organize code into smaller, reusable, and independent components. This makes applications easier to understand, test, extend, and maintain over time.
Benefits of following SOLID principles:
- Improves code readability.
- Reduces tight coupling between classes.
- Encourages code reuse.
- Makes unit testing easier.
- Simplifies adding new features.
- Reduces the chances of introducing bugs.
- Produces scalable and maintainable applications.
1. Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one responsibility or one reason to change.
If a class performs multiple unrelated tasks, modifying one feature may unintentionally affect another. Keeping responsibilities separate makes the code easier to maintain and test.
Real-World Example
Consider a restaurant. The chef prepares food, the cashier handles payments, and the waiter serves customers. If one person tries to perform all these tasks, efficiency decreases and mistakes become more likely.
Similarly, in software development, each class should focus on a single responsibility.
Bad Example
In the following example, the Employee class manages employee information,
saves data, and generates reports. This violates the Single Responsibility Principle.
public class Employee {
public void saveEmployee() {
System.out.println("Saving employee...");
}
public void generateReport() {
System.out.println("Generating report...");
}
}
If the database logic changes, this class must be modified. If the report format changes, the same class must be modified again. It now has multiple reasons to change.
Good Example
A better approach is to separate these responsibilities into different classes.
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class EmployeeRepository {
public void save(Employee employee) {
System.out.println("Employee saved.");
}
}
public class EmployeeReport {
public void generate(Employee employee) {
System.out.println("Generating employee report.");
}
}
Now each class has a single responsibility:
- Employee stores employee information.
- EmployeeRepository manages database operations.
- EmployeeReport generates reports.
2. Open/Closed Principle (OCP)
The Open/Closed Principle states that software entities such as classes, methods, and modules should be open for extension but closed for modification.
In simple terms, you should be able to add new functionality without changing existing, tested code. Modifying existing code repeatedly increases the risk of introducing bugs, especially in large applications.
Real-World Example
Consider a payment system in an e-commerce application. Initially, the application supports only Credit Card payments. Later, the business wants to add UPI, PayPal, and Net Banking.
If every new payment method requires modifying the existing payment class, the code becomes difficult to maintain. Instead, each payment method should be implemented separately while keeping the existing code unchanged.
Bad Example
public class PaymentService {
public void pay(String type) {
if(type.equals("CARD")) {
System.out.println("Credit Card Payment");
}
else if(type.equals("UPI")) {
System.out.println("UPI Payment");
}
else if(type.equals("PAYPAL")) {
System.out.println("PayPal Payment");
}
}
}
Every time a new payment option is introduced, this class must be modified. This violates the Open/Closed Principle.
Good Example
public interface Payment {
void pay();
}
public class CreditCardPayment implements Payment {
@Override
public void pay() {
System.out.println("Credit Card Payment");
}
}
public class UpiPayment implements Payment {
@Override
public void pay() {
System.out.println("UPI Payment");
}
}
public class PaymentProcessor {
public void process(Payment payment) {
payment.pay();
}
}
Usage
PaymentProcessor processor = new PaymentProcessor();
processor.process(new CreditCardPayment());
processor.process(new UpiPayment());
Now, adding a new payment method only requires creating another class that implements
the Payment interface. The existing code remains unchanged.
3. Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that a subclass should be able to replace its parent class without changing the correctness of the program.
In other words, if class B extends class A, then objects of class B should work correctly wherever objects of class A are expected.
Real-World Example
Imagine a car rental application. If a customer books a vehicle, they shouldn't need to know whether it's a Sedan, SUV, or Hatchback. Every vehicle should support common operations like starting and stopping.
If one vehicle suddenly throws an exception because it cannot perform a basic operation expected from every vehicle, the design violates the Liskov Substitution Principle.
Bad Example
class Bird {
public void fly() {
System.out.println("Flying...");
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException();
}
}
A penguin is a bird, but it cannot fly. Replacing a Bird object
with a Penguin object causes unexpected behavior.
This breaks the Liskov Substitution Principle.
Good Example
Instead of assuming every bird can fly, separate flying behavior from general bird behavior.
class Bird {
public void eat() {
System.out.println("Bird is eating.");
}
}
interface Flyable {
void fly();
}
class Sparrow extends Bird implements Flyable {
@Override
public void fly() {
System.out.println("Sparrow is flying.");
}
}
class Penguin extends Bird {
}
Now, only birds that can actually fly implement the
Flyable interface. Penguins are no longer forced
to provide an invalid implementation.
Why This Design Is Better
- Every subclass behaves correctly.
- No unexpected exceptions are thrown.
- Inheritance represents real-world behavior.
- The application becomes easier to maintain.
Key Takeaways
- SRP focuses on one responsibility per class.
- OCP allows adding new functionality without modifying existing code.
- LSP ensures subclasses can safely replace parent classes.
These three principles form the foundation of clean object-oriented design and are frequently discussed during Java and Spring Boot interviews.
4. Interface Segregation Principle (ISP)
The Interface Segregation Principle states that a class should not be forced to implement methods that it does not need. Instead of creating one large interface, create multiple smaller and more specific interfaces.
Large interfaces often become difficult to maintain because implementing classes are forced to provide unnecessary method implementations. Breaking them into focused interfaces keeps the design clean and flexible.
Real-World Example
Imagine an office where different employees have different responsibilities. A printer can print documents, while a scanner can scan documents. Forcing a printer to implement scanning functionality makes no sense.
Bad Example
public interface Machine {
void print();
void scan();
}
public class Printer implements Machine {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
throw new UnsupportedOperationException();
}
}
The Printer class is forced to implement the
scan() method even though it does not support scanning.
This violates the Interface Segregation Principle.
Good Example
public interface Printable {
void print();
}
public interface Scannable {
void scan();
}
public class Printer implements Printable {
@Override
public void print() {
System.out.println("Printing...");
}
}
public class MultiFunctionPrinter
implements Printable, Scannable {
@Override
public void print() {
System.out.println("Printing...");
}
@Override
public void scan() {
System.out.println("Scanning...");
}
}
Now each class implements only the functionality it actually requires, making the code more maintainable and easier to understand.
5. Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions such as interfaces or abstract classes.
In simple terms, classes should communicate through interfaces rather than depending directly on concrete implementations. This reduces coupling and makes applications easier to test and extend.
Real-World Example
Think about charging your smartphone. The phone does not depend on a specific charger brand. It depends on a standard charging port such as USB-C. Any compatible charger can be connected without changing the phone.
Similarly, Java classes should depend on interfaces rather than concrete classes.
Bad Example
public class MySQLDatabase {
public void save() {
System.out.println("Saving to MySQL");
}
}
public class UserService {
private MySQLDatabase database =
new MySQLDatabase();
public void saveUser() {
database.save();
}
}
If you decide to switch from MySQL to PostgreSQL or MongoDB,
you must modify the UserService class.
Good Example
public interface Database {
void save();
}
public class MySQLDatabase
implements Database {
@Override
public void save() {
System.out.println("Saving to MySQL");
}
}
public class UserService {
private Database database;
public UserService(Database database) {
this.database = database;
}
public void saveUser() {
database.save();
}
}
Usage
Database database = new MySQLDatabase();
UserService service =
new UserService(database);
service.saveUser();
The UserService class now depends on the
Database interface instead of a specific implementation.
Replacing the database requires no changes to the service class.
Benefits of SOLID Principles
- Improves code readability.
- Encourages reusable components.
- Reduces tight coupling.
- Makes applications easier to test.
- Simplifies maintenance.
- Supports scalable software architecture.
- Makes adding new features easier.
- Reduces the chances of introducing bugs.
Common Interview Questions
- What are the SOLID principles?
- Who introduced SOLID principles?
- What is the Single Responsibility Principle?
- Explain the Open/Closed Principle with an example.
- What is the difference between SRP and ISP?
- Give a real-world example of LSP.
- How does Spring Boot use Dependency Inversion?
- Which SOLID principle is achieved through Dependency Injection?
Frequently Asked Questions (FAQ)
Are SOLID principles only used in Java?
No. SOLID principles are language-independent object-oriented design principles and can be applied in languages such as C#, C++, Python, PHP, and Kotlin.
Do I need SOLID principles for small projects?
While small projects may work without them, following SOLID principles from the beginning helps create cleaner and more maintainable code as the project grows.
Are SOLID principles asked in interviews?
Yes. SOLID principles are among the most frequently asked Java and Spring Boot interview topics, especially for mid-level and senior developer roles.
Conclusion
SOLID principles provide a strong foundation for writing clean, scalable, and maintainable Java applications. By following SRP, OCP, LSP, ISP, and DIP, you can reduce code complexity, improve flexibility, and make your applications easier to extend and test.
Whether you are preparing for Java interviews or developing enterprise applications with Spring Boot, mastering SOLID principles will improve your software design skills and help you build high-quality, professional applications.
=SOLID Principles Cheat Sheet
| Principle | Short Description | Remember As |
|---|---|---|
| SRP | A class should have only one responsibility. | One Class โ One Job |
| OCP | Add new functionality without changing existing code. | Extend, Don't Modify |
| LSP | A child class should work wherever its parent is expected. | Proper Inheritance |
| ISP | Create small, focused interfaces. | Don't Force Unused Methods |
| DIP | Depend on interfaces, not concrete classes. | Program to Abstractions |
Real-World Examples of SOLID Principles
| Principle | Real-World Example |
|---|---|
| SRP | A cashier only handles billing, while a chef only prepares food. |
| OCP | Adding a new payment method without changing the existing payment system. |
| LSP | Any compatible USB keyboard should work when connected to a computer. |
| ISP | A printer shouldn't be forced to implement scanning functionality. |
| DIP | A mobile phone works with any compatible USB-C charger instead of one specific brand. |
Common Mistakes Beginners Make
- Creating large classes that handle multiple responsibilities.
- Using long
if-elseorswitchstatements instead of polymorphism. - Creating interfaces with too many unrelated methods.
- Depending directly on concrete classes instead of interfaces.
- Using inheritance where composition would be a better choice.
- Ignoring unit testing while designing classes.
Where Are SOLID Principles Used?
SOLID principles are widely used in enterprise Java applications and modern frameworks. You'll frequently see them in:
- Spring Boot applications
- REST API development
- Microservices architecture
- Android development
- Banking and financial software
- E-commerce platforms
- Hospital management systems
- Large-scale enterprise applications
SOLID Principles and Design Patterns
SOLID principles often work together with popular design patterns. Many Gang of Four (GoF) design patterns naturally follow one or more SOLID principles.
| Design Pattern | Related SOLID Principle |
|---|---|
| Strategy Pattern | Open/Closed Principle |
| Factory Pattern | Dependency Inversion Principle |
| Observer Pattern | Dependency Inversion Principle |
| Decorator Pattern | Open/Closed Principle |
| Adapter Pattern | Liskov Substitution Principle |
Quick Revision for Interviews
S - Single Responsibility Principle
One class should have one responsibility.
O - Open/Closed Principle
Open for extension, closed for modification.
L - Liskov Substitution Principle
A child class should replace its parent without breaking functionality.
I - Interface Segregation Principle
Use multiple small interfaces instead of one large interface.
D - Dependency Inversion Principle
Depend on abstractions, not concrete implementations.
Final Thoughts
Learning SOLID principles is more than memorizing five definitions. The real value lies in applying these principles while designing classes, writing business logic, and maintaining large codebases. Developers who consistently follow SOLID principles produce cleaner, more reusable, and easier-to-test applications.
As you continue learning Java and Spring Boot, practice identifying SOLID violations in your own projects and refactoring them into cleaner designs. Over time, these principles become a natural part of writing high-quality object-oriented code.