Advanced Java October 06 ,2025

SOLID Principles in Java — Building Strong Foundations for Scalable Software

When we talk about writing good Java code, most developers think of efficiency, performance, and readability.
But what truly differentiates a good developer from a great one is how well their code adapts to change.

Imagine this: you’ve built a car rental system for your client. Everything works fine — until they ask you to add electric cars, luxury cars, and dynamic pricing. Suddenly, your once-perfect code looks like a tangled web of if-else statements, duplicated logic, and fragile dependencies.

You sigh and think — “There must be a better way to design this.”
That’s exactly where SOLID principles come in.

What Are SOLID Principles?

SOLID is an acronym introduced by Robert C. Martin (Uncle Bob) to describe five key design principles in object-oriented programming. These principles help you write clean, maintainable, and scalable code that’s easy to test and extend.

LetterPrincipleDescription
SSingle Responsibility Principle (SRP)A class should have only one reason to change.
OOpen/Closed Principle (OCP)Code should be open for extension but closed for modification.
LLiskov Substitution Principle (LSP)Subclasses should be replaceable with their base classes.
IInterface Segregation Principle (ISP)No client should be forced to depend on methods it doesn’t use.
DDependency Inversion Principle (DIP)Depend on abstractions, not concrete implementations.

Now, let’s bring these principles to life with a story-based example.

The Story: Building a Ride Booking System

Let’s imagine you’re building “FastRide”, a simple ride-booking app like Uber.

Initially, you start with just one class — RideService.

public class RideService {
    public void bookCar(String type) {
        if (type.equals("normal")) {
            System.out.println("Booking a normal car...");
        } else if (type.equals("luxury")) {
            System.out.println("Booking a luxury car...");
        }
    }
}

You test it, and it works perfectly.
But then your client says, “Can we also add electric cars, and later maybe bike rides?”

Now, you find yourself modifying the same RideService class again and again — breaking existing functionality while adding new features.

Let’s apply the SOLID principles one by one to see how we can fix this.

1. Single Responsibility Principle (SRP)

“A class should have only one reason to change.”

The RideService class is handling too many things — booking, selecting vehicle type, and printing messages.
What if you decide to change how rides are booked or how vehicles are chosen? You’d have to modify the same class.

Let’s refactor.

Example

 (Before SRP)

public class RideService {
    public void bookCar(String type) {
        if (type.equals("normal")) {
            System.out.println("Booking a normal car...");
        } else if (type.equals("luxury")) {
            System.out.println("Booking a luxury car...");
        }
    }
}

 (After SRP)

We separate responsibilities into dedicated classes:

class VehicleSelector {
    public String selectVehicle(String type) {
        if (type.equals("normal")) return "Normal Car";
        if (type.equals("luxury")) return "Luxury Car";
        return "Unknown";
    }
}

class RideService {
    private VehicleSelector vehicleSelector = new VehicleSelector();
    public void bookRide(String type) {
        String vehicle = vehicleSelector.selectVehicle(type);
        System.out.println("Booking " + vehicle);
    }
}

Now, if tomorrow we need to change the vehicle selection logic, we only modify VehicleSelector, not RideService.
Each class now has one reason to change.

Advantages of SRP:

  • Easier debugging and maintenance.
  • Better readability and testability.
  • Low coupling between components.

Disadvantages:

  • More classes to manage.
  • Slightly higher initial development effort.

2. Open/Closed Principle (OCP)

“Software entities should be open for extension, but closed for modification.”

Your code should allow new features without changing existing code or if needed to change in existing code it should be very less.
In our example, we shouldn’t need to touch RideService every time a new vehicle type (like ElectricCar) is added.

Example

 (Before OCP)

public class RideService {
    public void bookCar(String type) {
        if (type.equals("normal")) {
            System.out.println("Booking a normal car...");
        } else if (type.equals("luxury")) {
            System.out.println("Booking a luxury car...");
        } else if (type.equals("electric")) {
            System.out.println("Booking an electric car...");
        }
    }
}

Every time a new vehicle type is added, this class changes — violating OCP.

 (After OCP)

Let’s use inheritance and polymorphism:

interface Vehicle {
    void book();
}

class NormalCar implements Vehicle {
    public void book() {
        System.out.println("Booking a normal car...");
    }
}

class LuxuryCar implements Vehicle {
    public void book() {
        System.out.println("Booking a luxury car...");
    }
}

class RideService {
    public void bookRide(Vehicle vehicle) {
        vehicle.book();
    }
}

Now, if you want to add ElectricCar, you just create a new class — no modification needed in RideService.

class ElectricCar implements Vehicle {
    public void book() {
        System.out.println("Booking an electric car...");
    }
}

Advantages of OCP:

  • Easier feature expansion.
  • Prevents bugs caused by modifying tested code.
  • Encourages use of interfaces and abstractions.

Disadvantages:

  • Can introduce many small classes.
  • Sometimes harder to trace logic for beginners.

3. Liskov Substitution Principle (LSP)

“Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.”

In our ride system, every Vehicle should behave like a Vehicle.
If we replace NormalCar with ElectricCar, the system should still work correctly.

Example 

(LSP Violation)

class Vehicle {
    void startEngine() {}
}

class ElectricBike extends Vehicle {
    @Override
    void startEngine() {
        throw new UnsupportedOperationException("Electric bikes have no engine!");
    }
}

Here, ElectricBike violates LSP — it doesn’t truly behave like a Vehicle.

 (Following LSP)

We should refactor the hierarchy to respect actual behaviors:

interface Vehicle {
    void start();
}

class Car implements Vehicle {
    public void start() {
        System.out.println("Starting the car engine...");
    }
}

class ElectricBike implements Vehicle {
    public void start() {
        System.out.println("Powering on electric bike...");
    }
}

Now, every subclass can be used interchangeably — all are valid Vehicles.

Advantages of LSP:

  • Ensures consistent and reliable subclass behavior.
  • Makes code more predictable and modular.

Disadvantages:

  • Hard to enforce for large, complex hierarchies.
  • Requires careful interface and class design.

4. Interface Segregation Principle (ISP)

“Clients should not be forced to depend on methods they do not use.”

Imagine you create a Vehicle interface with many methods:

interface Vehicle {
    void startEngine();
    void playMusic();
    void openSunroof();
}

Now, what happens if we implement this interface for Bike?

class Bike implements Vehicle {
    public void startEngine() { System.out.println("Starting bike engine..."); }
    public void playMusic() { /* Not applicable */ }
    public void openSunroof() { /* Not applicable */ }
}

The Bike class is forced to implement methods it doesn’t need — violating ISP.

 Example

 (Following ISP)

We can split the interfaces:

interface EngineVehicle {
    void startEngine();
}

interface MusicSystem {
    void playMusic();
}

interface Sunroof {
    void openSunroof();
}

class Bike implements EngineVehicle {
    public void startEngine() { System.out.println("Starting bike engine..."); }
}

class LuxuryCar implements EngineVehicle, MusicSystem, Sunroof {
    public void startEngine() { System.out.println("Starting car engine..."); }
    public void playMusic() { System.out.println("Playing music..."); }
    public void openSunroof() { System.out.println("Opening sunroof..."); }
}

Advantages of ISP:

  • Avoids “fat” interfaces.
  • Promotes modular and reusable design.
  • Classes depend only on what they need.

Disadvantages:

  • Too many small interfaces may lead to complexity.
  • Harder to maintain if not well-documented.

5. Dependency Inversion Principle (DIP)

“Depend upon abstractions, not upon concrete implementations.”

In our app, suppose RideService directly creates a NormalCar:

public class RideService {
    private NormalCar car = new NormalCar();
    public void book() {
        car.book();
    }
}

Now, if we want to use LuxuryCar, we must modify this class — tightly coupling it to implementations.

Following DIP

Let’s depend on abstraction instead:

interface Vehicle {
    void book();
}

class NormalCar implements Vehicle {
    public void book() {
        System.out.println("Booking a normal car...");
    }
}

class RideService {
    private Vehicle vehicle;
    public RideService(Vehicle vehicle) {
        this.vehicle = vehicle;
    }
    public void bookRide() {
        vehicle.book();
    }
}

Now, RideService doesn’t care whether it’s a NormalCar, LuxuryCar, or ElectricCar.

This enables Dependency Injection, which frameworks like Spring use to manage object lifecycles.

Advantages of DIP:

  • Reduces coupling between classes.
  • Increases flexibility and testability.
  • Encourages using interfaces and dependency injection.

Disadvantages:

  • Slightly more complex design.
  • Requires careful architecture planning.

Bringing It All Together

Here’s our final, SOLID-compliant ride system:

interface Vehicle {
    void book();
}

class NormalCar implements Vehicle {
    public void book() {
        System.out.println("Booking a normal car...");
    }
}

class ElectricCar implements Vehicle {
    public void book() {
        System.out.println("Booking an electric car...");
    }
}

class RideService {
    private Vehicle vehicle;

    public RideService(Vehicle vehicle) {
        this.vehicle = vehicle;
    }

    public void bookRide() {
        vehicle.book();
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle car = new ElectricCar();
        RideService rideService = new RideService(car);
        rideService.bookRide();
    }
}

Output:

Booking an electric car...

Now, your system is:

  • Easy to extend (OCP)
  • Consistent in behavior (LSP)
  • Modular (SRP, ISP)
  • Flexible and testable (DIP)

Advantages of Applying SOLID Principles

  • Better maintainability: Small, independent classes make debugging and enhancement easier.
  • Reusability: Each component can be reused in multiple contexts.
  • Scalability: Easy to add new features without touching old code.
  • Testability: Each unit can be tested in isolation.
  • Clean Architecture: Code reads more like a well-structured story than a random collection of logic.

Disadvantages

  • Initial setup can take longer.
  • May increase the number of classes and interfaces.
  • Over-engineering is a risk if applied blindly.
  • Requires good understanding of design patterns and architecture.

Conclusion

The SOLID principles aren’t just rules — they’re guidelines for long-term success.
They make your Java applications flexible, extensible, and easier to manage when requirements evolve.

Next time you find yourself changing one class to fix five others, stop and ask — “Am I being SOLID?”

 

 

Next Blog- Custom Implementation of HashSet in Java

 

Sanjiv
0

You must logged in to post comments.

Related Blogs

Generics P...
Advanced Java August 08 ,2025

Generics Part- 2

Collection...
Advanced Java July 07 ,2025

Collections Framewor...

Mastering...
Advanced Java August 08 ,2025

Mastering Java Multi...

Annotation...
Advanced Java August 08 ,2025

Annotations

Java Memor...
Advanced Java August 08 ,2025

Java Memory Manageme...

Java Lambd...
Advanced Java August 08 ,2025

Java Lambda Expressi...

Java Funct...
Advanced Java August 08 ,2025

Java Functional Inte...

Java Strea...
Advanced Java August 08 ,2025

Java Stream API

JDBC (Java...
Advanced Java August 08 ,2025

JDBC (Java Database...

JDBC (Java...
Advanced Java September 09 ,2025

JDBC (Java Database...

Annotation...
Advanced Java August 08 ,2025

Annotations

Generics
Advanced Java August 08 ,2025

Generics

Java I/O (...
Advanced Java August 08 ,2025

Java I/O (NIO)

Introducti...
Advanced Java September 09 ,2025

Introduction to Desi...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Other Prin...
Advanced Java September 09 ,2025

Other Principles Beh...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Creational...
Advanced Java September 09 ,2025

Creational Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

In Creatio...
Advanced Java September 09 ,2025

In Creational Design...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Builder De...
Advanced Java September 09 ,2025

Builder Design Patte...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Structural...
Advanced Java September 09 ,2025

Structural Design Pa...

Design Pat...
Advanced Java September 09 ,2025

Design Patterns in J...

Chain of R...
Advanced Java September 09 ,2025

Chain of Responsibil...

Command De...
Advanced Java September 09 ,2025

Command Design Patte...

Interprete...
Advanced Java September 09 ,2025

Interpreter Design P...

Iterator D...
Advanced Java September 09 ,2025

Iterator Design Patt...

Mediator D...
Advanced Java September 09 ,2025

Mediator Design Patt...

Memento De...
Advanced Java September 09 ,2025

Memento Design Patte...

Observer D...
Advanced Java September 09 ,2025

Observer Design Patt...

State Desi...
Advanced Java September 09 ,2025

State Design Pattern...

Strategy D...
Advanced Java September 09 ,2025

Strategy Design Patt...

Template M...
Advanced Java September 09 ,2025

Template Method Desi...

Visitor De...
Advanced Java September 09 ,2025

Visitor Design Patte...

Prototype...
Advanced Java September 09 ,2025

Prototype Design Pat...

Java 8+ Fe...
Advanced Java October 10 ,2025

Java 8+ Features

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

Custom Imp...
Advanced Java October 10 ,2025

Custom Implementatio...

How Iterat...
Advanced Java October 10 ,2025

How Iterators Work i...

How Concur...
Advanced Java October 10 ,2025

How ConcurrentHashMa...

Comparable...
Advanced Java October 10 ,2025

Comparable vs Compar...

Get In Touch

G06, Kristal Olivine Bellandur near Bangalore Central Mall, Bangalore Karnataka, 560103

+91-8076082435

techiefreak87@gmail.com