• Skip to main content
  • Skip to secondary menu
  • Skip to primary sidebar
  • Skip to footer
  • Home
  • Contact Us

All about code

  • Java
    • Spring
      • Spring Boot
      • Spring Cloud
    • Java Miscellaneous Tips
    • Java Design Patterns
  • Data Structures and Algorithms
    • Algorithms
    • Data Structures
  • Thoughts and Stories
  • AI

SOLID Principles Through Java Code Examples

coffee cup and saucer beverage tea 24961

Introduction

Writing clean, scalable, and maintainable object-oriented code is a key challenge in software development. To help guide developers, Robert C. Martin introduced the SOLID principles, five core design principles that lead to better software architecture. When applied properly, these principles make code easier to understand, extend, and refactor, reducing the chances of introducing bugs. In this article, we’ll explore each SOLID principle using simple and original Java examples to illustrate the correct and incorrect ways to implement them.

Single Responsibility Principle

A class should have only one reason to change, meaning it should only have one job or responsibility. This makes the system easier to maintain and extend.

// Violates SRP: Combines data and output logic
class Report {
    String content;

    Report(String content) {
        this.content = content;
    }

    void printReport() {
        System.out.println(content);
    }
}
// Follows SRP: Splits responsibilities across classes
class Report {
    String content;

    Report(String content) {
        this.content = content;
    }

    String getContent() {
        return content;
    }
}

class ReportPrinter {
    void print(Report report) {
        System.out.println(report.getContent());
    }
}

Open/Closed Principle

Software entities should be open for extension but closed for modification. You should be able to introduce new behavior without altering existing, tested code.

// Violates OCP: Requires modification to support new types
class DiscountCalculator {
    double calculate(String type, double amount) {
        if (type.equals("student")) {
            return amount * 0.85;
        } else if (type.equals("senior")) {
            return amount * 0.75;
        }
        return amount;
    }
}
// Follows OCP: New behavior added by extending interface
interface DiscountStrategy {
    double apply(double amount);
}

class StudentDiscount implements DiscountStrategy {
    public double apply(double amount) {
        return amount * 0.85;
    }
}

class SeniorDiscount implements DiscountStrategy {
    public double apply(double amount) {
        return amount * 0.75;
    }
}

class DiscountCalculator {
    double calculate(DiscountStrategy strategy, double amount) {
        return strategy.apply(amount);
    }
}

Liskov Substitution Principle

Subtypes must be substitutable for their base types without affecting program correctness. If a subclass violates expected behavior, it breaks the design.

// Violates LSP: Subclass breaks expected behavior
class Bird {
    void fly() {
        System.out.println("Flying");
    }
}

class Penguin extends Bird {
    void fly() {
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}
// Follows LSP: Separates flying and non-flying birds via interface
interface Bird {}

interface FlyableBird extends Bird {
    void fly();
}

class Eagle implements FlyableBird {
    public void fly() {
        System.out.println("Eagle flying");
    }
}

class Penguin implements Bird {
    // No fly method, which respects its natural behavior
}

Interface Segregation Principle

Clients should not be forced to implement interfaces they don’t use. It’s better to have several small, role-specific interfaces than one large, general-purpose one.

// Violates ISP: Forces implementation of unused methods
interface PrintMachine {
    void print();
    void scan();
    void fax();
}

class BasicPrinter implements PrintMachine {
    public void print() {
        System.out.println("Printing...");
    }
    public void scan() {
        throw new UnsupportedOperationException();
    }
    public void fax() {
        throw new UnsupportedOperationException();
    }
}
// Follows ISP: Interfaces separated by responsibility
interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

class SimplePrinter implements Printer {
    public void print() {
        System.out.println("Simple printing...");
    }
}

class AllInOneMachine implements Printer, Scanner {
    public void print() {
        System.out.println("Printing...");
    }
    public void scan() {
        System.out.println("Scanning...");
    }
}

Dependency Inversion Principle

High-level modules should depend on abstractions, not concrete implementations. This promotes loose coupling and makes systems easier to test and maintain.

// Violates DIP: Tightly coupled to a concrete class
class MySQLDatabase {
    void connect() {
        System.out.println("Connected to MySQL");
    }
}

class UserService {
    MySQLDatabase db = new MySQLDatabase();

    void register() {
        db.connect();
        System.out.println("User registered");
    }
}
// Follows DIP: Depends on abstraction, not implementation
interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() {
        System.out.println("Connected to MySQL");
    }
}

class UserService {
    private final Database db;

    UserService(Database db) {
        this.db = db;
    }

    void register() {
        db.connect();
        System.out.println("User registered");
    }
}

Conclusion

The SOLID principles are not rules enforced by the compiler, but design guidelines that promote better architecture and maintainability. Each principle addresses a specific aspect of object-oriented design, helping you avoid rigidity, fragility, and unnecessary complexity. By understanding and applying these principles thoughtfully in your Java code, you can build systems that are easier to understand, change, test, and scale. While it’s not always necessary to apply all five principles at once, recognizing when they matter most is what sets apart robust software from fragile implementations.

Primary Sidebar

Social

  • Facebook
  • LinkedIn
  • Twitter

Archives

  • May 2026
  • April 2026
  • March 2026
  • August 2025
  • July 2025
  • June 2025
  • May 2025
  • April 2025
  • March 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • September 2023
  • May 2023
  • March 2023
  • January 2023
  • November 2022
  • September 2022
  • August 2022

Recent Posts

  • How AI Coding Assistants Are Reshaping Software Engineering Careers
  • Better Avoid Spring Boot Lazy-initialization @PostConstruct Trap
  • How To Handle Large Datasets in Spring Boot
  • How to Log HTTP Incoming Requests in Spring Boot
  • How to Reliably Implement Post-Commit Actions in Spring

TAGS

AI Algorithms Apache POI Backtracking Date Structures Dynamic Programming engineering Graphs Greedy Horror Stories HSSF Java Java New Features Java Principles Java tips real life Sorting Spring Boot Spring Cloud Strings Trees

Footer

Privacy Policy Cookie Policy Terms and Conditions