SOLID Principles in Java Explained with Examples | Beginner to Advanced Guide

Reading Time:13-15 minutes

SOLID is a set of five design principles that help developers write clean, maintainable, scalable, and loosely coupled software. These principles are widely used in object-oriented programming and are very important for Java developers, especially when building enterprise applications using frameworks like Spring.

SOLID helps solve common software problems like tightly coupled code, difficult maintenance, poor scalability, and hard-to-test systems.

SOLID stands for:

S — Single Responsibility Principle
O — Open Closed Principle
L — Liskov Substitution Principle
I — Interface Segregation Principle
D — Dependency Inversion Principle

This tutorial explains each principle with simple examples so that even beginners can understand easily.

Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change, meaning a class should have only one responsibility.

If a class performs multiple tasks, changing one responsibility may affect another. This makes the system difficult to maintain.

Bad Example (Multiple Responsibilities in One Class)


package com.codekatha;

class EmployeeService {

    public void calculateSalary() {
        System.out.println("Calculating salary");
    }

    public void saveEmployeeToDatabase() {
        System.out.println("Saving employee to database");
    }

    public void printEmployeeReport() {
        System.out.println("Printing employee report");
    }
}

This class has three responsibilities: salary calculation, database storage, and reporting. Any change in one feature forces modification of the same class.

Good Example (Separate Responsibilities)


package com.codekatha;

class SalaryCalculator {
    public void calculateSalary() {
        System.out.println("Calculating salary");
    }
}

class EmployeeRepository {
    public void saveEmployee() {
        System.out.println("Saving employee to database");
    }
}

class EmployeeReportPrinter {
    public void printReport() {
        System.out.println("Printing employee report");
    }
}

Now each class has one responsibility, making the system easier to maintain and test.

Open Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.

This means we should be able to add new functionality without changing existing code.

Bad Example (Modifying Code for Every New Shape)


package com.codekatha;

class AreaCalculator {

    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            return 3.14 * 10 * 10;
        }
        if (shape instanceof Square) {
            return 4 * 4;
        }
        return 0;
    }
}

If a new shape like Rectangle is added, we must modify this class. This violates the Open Closed Principle.

Good Example (Using Interface for Extension)


package com.codekatha;

interface Shape {
    double area();
}

class Circle implements Shape {
    public double area() {
        return 3.14 * 10 * 10;
    }
}

class Square implements Shape {
    public double area() {
        return 4 * 4;
    }
}

Now new shapes can be added without changing existing code.

Liskov Substitution Principle (LSP)

Definition: Subclasses should be replaceable with their base classes without affecting program correctness.

If a subclass changes expected behavior, it violates this principle.

Bad Example


package com.codekatha;

class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Ostrich extends Bird {
    public void fly() {
        throw new UnsupportedOperationException("Ostrich cannot fly");
    }
}

An Ostrich cannot fly, so using it as a Bird breaks expected behavior.

Good Example


package com.codekatha;

interface Bird {
}

interface FlyingBird {
    void fly();
}

class Sparrow implements Bird, FlyingBird {
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

class Ostrich implements Bird {
}

Now behavior is correctly separated.

Interface Segregation Principle (ISP)

Definition: Clients should not be forced to implement methods they do not use.

Large interfaces should be split into smaller, specific interfaces.

Bad Example


package com.codekatha;

interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {

    public void work() {
        System.out.println("Robot working");
    }

    public void eat() {
        System.out.println("Robot does not eat");
    }
}

Robot does not need the eat method.

Good Example


package com.codekatha;

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Robot implements Workable {
    public void work() {
        System.out.println("Robot working");
    }
}

Interfaces are now specific and clean.

Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.

This principle reduces tight coupling by using interfaces.

Bad Example (Tight Coupling)


package com.codekatha;

class PetrolEngine {
    public void start() {
        System.out.println("Petrol engine started");
    }
}

class Car {
    private PetrolEngine engine = new PetrolEngine();

    public void start() {
        engine.start();
    }
}

Car is tightly dependent on PetrolEngine.

Good Example (Using Abstraction)


package com.codekatha;

interface Engine {
    void start();
}

class PetrolEngine implements Engine {
    public void start() {
        System.out.println("Petrol engine started");
    }
}

class DieselEngine implements Engine {
    public void start() {
        System.out.println("Diesel engine started");
    }
}

class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

Now the Car class depends on an interface, not a specific engine.

Benefits of SOLID Principles

Following SOLID principles provides multiple advantages:

Improves code maintainability
Reduces tight coupling
Makes testing easier
Improves scalability
Encourages clean architecture
Enhances flexibility

Simple Summary of SOLID

Single Responsibility: One class should have one responsibility.
Open Closed: Extend behavior without modifying existing code.
Liskov Substitution: Subclasses should behave like parent classes.
Interface Segregation: Use small and specific interfaces.
Dependency Inversion: Depend on abstractions, not concrete classes.

Conclusion

SOLID principles form the foundation of good object-oriented design. They help developers build systems that are flexible, maintainable, and easy to extend. Mastering SOLID improves both coding quality and system architecture, making it an essential skill for every Java developer.

Once you understand SOLID well, design patterns like Factory, Strategy, and Dependency Injection become easier to understand because they are built on these principles.

Comments

Popular Posts on Code Katha

Java Interview Questions for 10 Years Experience

Sql Interview Questions for 10 Years Experience

Spring Boot Interview Questions for 10 Years Experience

Java interview questions - Must to know concepts

Visual Studio Code setup for Java and Spring with GitHub Copilot

Spring AI with Ollama

Data Structures & Algorithms Tutorial with Coding Interview Questions

Spring Data JPA

Topological Sort in Graph

Bit Manipulation and Bit Masking Concepts