SOLID Design Principles in Java – A Complete Guide with Examples
SOLID is a set of five object-oriented design principles that help developers design clean, maintainable, scalable, and flexible software systems. These principles reduce tight coupling between classes, improve code readability, and make applications easier to extend and test.

It is a set of 5 object-oriented design principles that help you write clean, maintainable, scalable, and testable Java code. Design patterns are often built on top of SOLID principles.
- S :Single Responsibility Principle (SRP)
- O:Open/Closed Principle (OCP)
- L:Liskov Substitution Principle (LSP)
- I : Interface Segregation Principle (ISP)
- D:Dependency Inversion Principle (DIP)
1.Single Responsibility Principle (SRP)
there should never be more than one functionality for a class to change , single responsibility focused , single logic/functionality addresses a specific concern .
Example:
We have written a code in a single class that code is actually creating and sending a message to remote server . that listing on some port .
What are the possible reason to change the class .
- if communication protocol change then we have to change the class .
- message format is change then we have to change the format . (JSON == > XML)
- if Authentication mechanism change then we have to changed the class.
- conclusion is that Single responsibility principle telling you have multiple responsibility then you have to create multiple classes .
1.UserController.java
package com.test.rkdigital.school;
import java.io.IOException;
public class UserController {
private userPersistenceService service= new userPersistenceService();
public String saveUser(String payload)throws IOException
{
objectmapper mapper=new objectMapper();
User user=mapper.readValue(payload,User.class)
Uservalidator validator=new UserValidator();
boolean valid=validator.validateUser(user);
if(!valid) {
return "ERROR";
}
service(saveUser(user));
return "SUCCESS";
}
}
- User.java
package com.test.rkdigital.school;
public class User {
private String name;
private String email;
private String address;
public User(String name,String email, String address) {
// TODO Auto-generated constructor stub
this.name=name;
this.email=email;
this.address=address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2.UserpersistenceService.java
package com.test.rkdigital.school;
public class UserpersistenceService
{
private Store store=new store();
public void saveUser(User user)
{
store.store(user);
}
}
4.Store.java
package com.test.rkdigital.school;
import java.util.HashMap;
import java.util.Map;
public class Store {
private static final Map<String, User> storage=new HashMap<>();
public void store(User user) {
synchronized (storage) {
storage.put(user.getName(), user);
}
}
public User getUser(String name) {
synchronized (storage) {
return storage.get(name);
}
}
}
5.UserValidaor.java
package com.test.rkdigital.school;
public class userValidator {
public boolean validateUser(User user) {
return isValidUser(user);
}
private boolean isValidUser(User user) {
if("null"!=user.getName()) {
return false;
}
user.setName(user.getName().trim());
return true;
}
}
2. Open Closed Principle
Open-closed principle should be open for extension but closed for modification . open for extension means extended existing behavior. closed for modification means existing code remain un change.
This means:
- You should be able to add new behavior
- Without changing existing, tested code

Suppose you have written one method in base class , you want to modify that logic we should override in child class and change the logic .
For example, consider a use case where a discount module has been implemented with festival and loyalty discounts. In the future, if additional discount types are introduced .
the system should allow their inclusion without requiring any modifications to the existing implementation
Example
1: Create an abstraction
interface DiscountStrategy {
double applyDiscount(double amount);
}
2. Create implementations (Extend behavior)
Class FestivalDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double amount) {
return amount * 0.20;
}
}
class LoyaltyDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double amount) {
return amount * 0.10;
}
}
3: Use abstraction in calculator
class DiscountCalculator {
public double calculate(DiscountStrategy discount, double amount) {
return discount.applyDiscount(amount);
}
}
4: Client code
public class Main {
public static void main(String[] args) {
DiscountCalculator calculator = new DiscountCalculator();
double festival = calculator.calculate(new FestivalDiscount(), 1000);
double loyalty = calculator.calculate(new LoyaltyDiscount(), 1000);
System.out.println(festival);
System.out.println(loyalty);
}
}
3.Liskov Substitution principle
We should be to substitute base class objects with child class objects . and this should not alter behavior of program.
Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.
Why LSP Is Important
- Ensures proper use of inheritance
- Prevents unexpected runtime errors
- Improves code reliability and maintainability
- Supports polymorphism
Example : Liskov Substitution Principle with Violation
- Rectangle.java
package com.test.rkdigital.school;
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
// TODO Auto-generated constructor stub
this.width=width;
this.height=height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int computeArea() {
return width*height;
}
}
2.Square.java
package com.test.rkdigital.school;
public class Square extends Rectangle{
public Square(int side) {
// TODO Auto-generated constructor stub
super(side,side);
}
@Override
public void setWidth(int width) {
setSide(width);
}
@Override
public void setHeight(int width) {
setSide(width);
}
private void setSide(int side) {
// TODO Auto-generated method stub
super.setWidth(side);
super.setHeight(side);
}
}
3. Lspmain.java
package com.test.rkdigital.school;
public class Lspmain {
public static void main(String[] args) {
Rectangle rectangle=new Rectangle(10, 20);
System.out.println(rectangle.computeArea());
System.out.println("******************");
Square square=new Square(10);
System.out.println(square.computeArea());
useRectangle(rectangle);
useRectangle(square);
}
public static void useRectangle(Rectangle rectangle) {
rectangle.setWidth(20);
rectangle.setHeight(30);
}
}
when you run the above program its violet the liskov substitution rule . to overcome this solution we will use abstraction using interface .below example solve the above problem .
1.Shape.java
package com.test.rkdigital.school;
public interface Shape {
public int computeArea();
}
2.Rectangle.java
package com.test.rkdigital.school;
public class Rectangle implements Shape{
private int width;
private int height;
public Rectangle(int width, int height) {
// TODO Auto-generated constructor stub
this.width=width;
this.height=height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public int computeArea() {
return width*height;
}
}
3.Square.java
package com.test.rkdigital.school;
public class Square implements Shape{
private int side;
public Square(int side) {
this.side=side;
}
public int getSide() {
return side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public int computeArea() {
// TODO Auto-generated method stub
return side*side;
}
}
4.Interface Segregation principle
clients should not be forced to depend upon interfaces that they do not use .
Interface Pollution : we should not make our interface bigger do not write any unrelated and unused method .
Sign of Interface Segregation violation
- classes have empty method implementation .
- method implementations throws unsupported operation Exception .
- method implementations return null or default/dummy values .
1.Entity.java
package com.rkdigitalschool.entity;
public abstract class Entity {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
2.Order.java
package com.rkdigitalschool.entity;
import java.time.LocalDateTime;
public class Order extends Entity {
private LocalDateTime orderPlacedOn;
private double totalValue;
public LocalDateTime getOrderPlacedOn() {
return orderPlacedOn;
}
public void setOrderPlacedOn(LocalDateTime orderPlacedOn) {
this.orderPlacedOn = orderPlacedOn;
}
public double getTotalValue() {
return totalValue;
}
public void setTotalValue(double totalValue) {
this.totalValue = totalValue;
}
}
3.User.java
package com.company.intersegrega.entity;
import java.time.LocalDateTime;
public class User extends Entity{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.PersistenceService.java
package com.rkdigitalschool.service;
import java.util.List;
import com.rkdigitalschool.entity.Entity;
public interface PersistenceService<T extends Entity> {
public void save(T entity);
public void delete(T entity);
public T findById(Long id);
public List<T> findByName(String name); // remove from this class
}
- if you add findByName() method here it will violet the interface segregation principle .
- if you implement multiple EntitypersistenceService then you have to implement every EntitypersistenceService.
- you have to remove from persistenceService class and have to implement in particular class .
5.OrderPersistenceService.java
package com.rkdigitalschool.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.rkdigitalschool.entity.Order;
public class OrderPersistenceService implements PersistenceService<Order>{
private static final Map<Long, Order> OrderS = new HashMap<>();
@Override
public void save(Order entity) {
synchronized (OrderS) {
OrderS.put(entity.getId(), entity);
}
}
@Override
public void delete(Order entity) {
synchronized (OrderS) {
OrderS.remove(entity.getId());
}
}
@Override
public Order findById(Long id) {
synchronized (OrderS) {
return OrderS.get(id);
}
}
}
6.UserPersistenceService.java
package com.rkdigitalschool.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.rkdigitalschool.entity.User;
public class UserPersistenceService implements PersistenceService<User>{
private static final Map<Long, User> UserS = new HashMap<>();
@Override
public void save(User entity) {
synchronized (UserS) {
UserS.put(entity.getId(), entity);
}
}
@Override
public void delete(User entity) {
synchronized (UserS) {
UserS.remove(entity.getId());
}
}
@Override
public User findById(Long id) {
synchronized (UserS) {
return UserS.get(id);
}
}
@Override
public List<User> findByName(String name) {
synchronized (UserS) {
return UserS.values().stream().filter(u->u.getName().equalsIgnoreCase(name)).collect(Collectors.toList());
}
}
}
5.Dependency Inversion principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
Example :
public void writeReport() {
Report report=new Report();
JsonFormatter formatter = new JsonFormatter();
String report = formatter.format(report);
Filewriter writer = new Filewriter("report.json");
}
Above example tightly couple ,means its depend on the formatter object . creating the object in low level implementation its depend on low level component .
Above code we are instantiating jsonformatter and filewriter object inside method .
- High level module that means , a module that provides or implement some business rule .
- A low level module basically that means , a functionality so basic that can be use any where example converting java object to json, it low level module .
- Out High level module should not depend on low level module .
- In that case we will create interface and used every where .in above code you will pass different different formatter .
package com.test.rkdigital.school.solid;
import jdk.internal.net.http.hpack.Huffman.Writer;
public class WriteReportImpl {
public void writeReport(Formatter formatter, Writer writer) {
Report report=new Report();
String myreport = formatter.format(report);
writer.write(myreport);
}
}
Example of Dependency Inversion Principle Implementation
1. Create formatter abstraction
package com.test.rkdigital.school.solid;
public interface Formatter {
public String format(Message message);
}
2.Message.java
package com.test.rkdigital.school.solid;
public class Message {
private String content;
Message(String content){
this.content=content;
}
public String getContent() {
return content;
}
}
3.JsonFormatter.java
package com.test.rkdigital.school.solid;
import java.io.IOException;
public class JsonFormatter implements Formatter{
public String format(Message message)throws IOException
{
ObjectMapper mapper=new ObjectMapper();
try {
mapper.writeValueAsString(message);
}catch(IOException e) {
e.printStackTrace();
throw new Exception();
}
}
}
4.TextFormatter.java
package com.test.rkdigital.school.solid;
import java.io.IOException;
public class TextFormatter implements Formatter{
@Override
public String format(Message message) throws IOException {
// TODO Auto-generated method stub
return message.getContent();
}
}
5.MessagePrinter.java
package com.test.rkdigital.school.solid;
import java.io.PrintWriter;
public class MessagePrinter {
public void writeMessage(Message msg, Formatter formatter , PrintWriter writer) {
writer.println(formatter.format(msg));
writer.flush();
}
}
6.MainTest.java
package com.test.rkdigital.school.solid;
import java.io.PrintWriter;
public class MainTest {
public static void main(String[] args) {
Message msg=new Message("Rk digital school tutorials");
MessagePrinter printer=new MessagePrinter();
try(PrintWriter writer=new PrintWriter(System.out))
{
printer.writeMessage(msg, new JsonFormatter(), writer);
}
}
}