Fundamentals 18 min read

The Seven SOLID Principles in Java Software Design

This article explains the seven SOLID principles of Java software design—Single Responsibility, Open‑Closed, Liskov Substitution, Interface Segregation, Dependency Inversion, Law of Demeter, and Composite Reuse—illustrating each with clear Java code examples that demonstrate how to write more maintainable, extensible, and flexible object‑oriented code.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
The Seven SOLID Principles in Java Software Design

The seven SOLID principles are essential guidelines for object‑oriented design in Java, ensuring software that is extensible, maintainable, reusable, and flexible.

1. Single Responsibility Principle (SRP)

SRP states that a class should have only one reason to change, i.e., it should handle a single responsibility. This improves readability, maintainability, and extensibility.

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // getters and setters omitted
}
public class Grade {
    private String courseName;
    private double score;
    public Grade(String courseName, double score) {
        this.courseName = courseName;
        this.score = score;
    }
    // getters and setters omitted
}

Separating student information and grade information into distinct classes keeps each class small and clear, making future extensions easier.

2. Open‑Closed Principle (OCP)

OCP advises that software entities should be open for extension but closed for modification. New functionality is added by extending existing code rather than altering it.

abstract class Shape {
    public abstract void draw();
}

class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Draw a circle");
    }
}

class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Draw a rectangle");
    }
}

class Drawer {
    public void drawShape(Shape shape) {
        shape.draw();
    }
}

public class OCPExample {
    public static void main(String[] args) {
        Drawer drawer = new Drawer();
        Shape circle = new Circle();
        drawer.drawShape(circle); // Output: Draw a circle
        Shape rectangle = new Rectangle();
        drawer.drawShape(rectangle); // Output: Draw a rectangle
    }
}

The drawer works with the abstract Shape type, so new shapes can be added without changing the drawer code.

3. Liskov Substitution Principle (LSP)

LSP requires that objects of a subclass can replace objects of the superclass without affecting program correctness.

public class Shape {
    public double getArea() { return 0; }
}

public class Rectangle extends Shape {
    private double width;
    private double height;
    public void setWidth(double width) { this.width = width; }
    public void setHeight(double height) { this.height = height; }
    @Override
    public double getArea() { return width * height; }
}

public class Square extends Shape {
    private double side;
    public void setSide(double side) { this.side = side; }
    @Override
    public double getArea() { return side * side; }
}
Shape rectangle = new Rectangle();
rectangle.setWidth(5);
rectangle.setHeight(10);
System.out.println(rectangle.getArea()); // 50

Shape square = new Square();
square.setSide(5);
System.out.println(square.getArea()); // 25

Both Rectangle and Square> can be used wherever a Shape is expected.

4. Dependency Inversion Principle (DIP)

DIP states that high‑level modules should not depend on low‑level modules; both should depend on abstractions.

// Low‑level module interface
public interface InterfaceB {
    void doSomething();
}

// Concrete low‑level implementation
public class InterfaceBImpl implements InterfaceB {
    @Override
    public void doSomething() {
        System.out.println("InterfaceB implementation");
    }
}

// High‑level module depending on abstraction
public class InterfaceA {
    private InterfaceB interfaceB;
    public InterfaceA(InterfaceB interfaceB) {
        this.interfaceB = interfaceB;
    }
    public void doSomething() {
        interfaceB.doSomething();
    }
}

Now InterfaceA depends on the abstract InterfaceB , allowing different implementations to be injected without changing high‑level code.

5. Interface Segregation Principle (ISP)

ISP recommends that clients should not be forced to depend on interfaces they do not use.

public interface Shape {
    void draw();
    double calculateArea();
}

public class Circle implements Shape {
    @Override
    public void draw() { System.out.println("Draw circle"); }
    @Override
    public double calculateArea() { /* implementation */ }
}

public class Rectangle implements Shape {
    @Override
    public void draw() { System.out.println("Draw rectangle"); }
    @Override
    public double calculateArea() { /* implementation */ }
}

public class Triangle implements Shape {
    @Override
    public void draw() { System.out.println("Draw triangle"); }
    @Override
    public double calculateArea() { /* implementation */ }
}

public class DrawingTool {
    private List
shapes;
    public DrawingTool(List
shapes) { this.shapes = shapes; }
    public void drawAllShapes() {
        for (Shape shape : shapes) { shape.draw(); }
    }
}

public class Main {
    public static void main(String[] args) {
        List
shapes = new ArrayList<>();
        shapes.add(new Circle());
        shapes.add(new Rectangle());
        shapes.add(new Triangle());
        DrawingTool drawingTool = new DrawingTool(shapes);
        drawingTool.drawAllShapes();
    }
}

Each class implements only the methods it needs via the small Shape interface.

6. Law of Demeter (LoD)

LoD (Least Knowledge) encourages minimal knowledge of other objects, reducing coupling.

public class Student {
    private String name;
    private int age;
    private String school;
    public Student(String name, int age, String school) {
        this.name = name; this.age = age; this.school = school;
    }
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getSchool() { return school; }
    public void introduce() {
        System.out.println("I am " + name + ", " + age + " years old, studying at " + school);
    }
}

public class Class {
    private String className;
    private List
students;
    public Class(String className, List
students) {
        this.className = className; this.students = students;
    }
    public void printStudents() {
        for (Student s : students) { System.out.println(s.getName()); }
    }
}

public class School {
    private String name;
    private List
classes;
    public School(String name, List
classes) {
        this.name = name; this.classes = classes;
    }
    public void printClassNames() {
        for (Class c : classes) { System.out.println(c.getClassName()); }
    }
}

Each class interacts only with its immediate collaborators, adhering to LoD.

7. Composite Reuse Principle (CRP)

CRP favors composition over inheritance for code reuse.

public interface Shape { void draw(); }

public class Circle implements Shape {
    @Override public void draw() { System.out.println("Draw a circle"); }
}

public class Rectangle implements Shape {
    @Override public void draw() { System.out.println("Draw a rectangle"); }
}

public class CompositeShape implements Shape {
    private List
shapes = new ArrayList<>();
    public void addShape(Shape shape) { shapes.add(shape); }
    public void removeShape(Shape shape) { shapes.remove(shape); }
    @Override public void draw() { for (Shape s : shapes) { s.draw(); } }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();
        CompositeShape composite = new CompositeShape();
        composite.addShape(circle);
        composite.addShape(rectangle);
        composite.draw(); // Outputs: Draw a circle \n Draw a rectangle
    }
}

By composing basic shapes into a CompositeShape , complex drawings are built without deep inheritance hierarchies.

Conclusion

The seven SOLID principles provide a solid foundation for writing high‑quality, maintainable, and extensible Java code.

Javasoftware architecturedesign principlesobject-orientedSOLID
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.