On this page

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) is the last of the five SOLID principles. It says that your important business code should not depend on low-level details. Both should depend on abstractions (interfaces).

Definition

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.

  • High-level module: the business logic (what the app does), such as placing an order.
  • Low-level module: the details (how it is done), such as MySQL, an email service, or a payment API.

Normally, the business code calls the details directly, so it depends on them. DIP inverts this: the business code defines an interface, and the details implement it.

Analogy: a lamp does not wire itself to the power station. It has a standard plug, and any socket that fits will work. The lamp depends on the plug standard (the abstraction), not on a specific power source.

Why It Matters

When business code is tied to a concrete detail:

  • Changing the detail (a new database, a new payment provider) forces you to edit the business code.
  • Testing is hard. To test an order, you need a real database and a real email server.
  • Reuse is hard. The business logic cannot move to another project without its details.

A Violation

public class MySqlDatabase {
public void save(Order order) {
// write to MySQL
}
}
public class EmailService {
public void send(String to, String message) {
// send a real email
}
}
public class OrderService {
// The high-level class creates its own low-level details
private MySqlDatabase database = new MySqlDatabase();
private EmailService emailService = new EmailService();
public void placeOrder(Order order) {
database.save(order);
emailService.send(order.getCustomerEmail(), "Order placed");
}
}

OrderService is glued to MySQL and to this exact email class:

  • Moving to PostgreSQL means editing OrderService.
  • A unit test of placeOrder really writes to a database and sends an email.
1Before: OrderService ----> MySqlDatabase (business depends on a detail)

The Fix

Step 1: the high-level code defines what it needs as an interface. Step 2: the details implement that interface. Step 3: the dependency is passed in from outside (dependency injection).

// Abstractions, owned by the business layer
public interface OrderRepository {
void save(Order order);
}
public interface NotificationSender {
void send(String to, String message);
}
// Details implement the abstractions
public class MySqlOrderRepository implements OrderRepository {
public void save(Order order) { /* write to MySQL */ }
}
public class EmailNotificationSender implements NotificationSender {
public void send(String to, String message) { /* send an email */ }
}
// High-level code depends only on abstractions
public class OrderService {
private final OrderRepository repository;
private final NotificationSender sender;
public OrderService(OrderRepository repository, NotificationSender sender) {
this.repository = repository;
this.sender = sender;
}
public void placeOrder(Order order) {
repository.save(order);
sender.send(order.getCustomerEmail(), "Order placed");
}
}
1After: OrderService ----> OrderRepository <---- MySqlOrderRepository
2 (interface)

The arrow from the detail now points up to the interface. That is the inversion.

Easy to Swap

To move to PostgreSQL or a different notification channel, add a new class. OrderService does not change.

public class PostgresOrderRepository implements OrderRepository {
public void save(Order order) { /* write to PostgreSQL */ }
}
public class SmsNotificationSender implements NotificationSender {
public void send(String to, String message) { /* send an SMS */ }
}
// Only the wiring changes
OrderService service = new OrderService(
new PostgresOrderRepository(),
new SmsNotificationSender()
);

Easy to Test

In a unit test, use fake versions. There is no database and no email server:

@Test
void placeOrderSavesAndNotifies() {
FakeOrderRepository repository = new FakeOrderRepository();
FakeNotificationSender sender = new FakeNotificationSender();
OrderService service = new OrderService(repository, sender);
service.placeOrder(new Order("asha@example.com"));
assertEquals(1, repository.savedOrders().size());
assertEquals(1, sender.sentMessages().size());
}

DIP vs Dependency Injection vs IoC

These three names are often mixed up:

TermWhat it is
Dependency Inversion (DIP)A design principle: depend on abstractions, not on concrete details
Dependency Injection (DI)A technique: pass the dependencies from outside (usually through the constructor)
Inversion of Control (IoC)A wider idea: a framework or container controls object creation, instead of your code

Spring is an IoC container. It creates the beans and injects them, so with @Service and constructor injection you get DI almost for free.

@Service
public class OrderService {
private final OrderRepository repository; // an interface, not a concrete class
public OrderService(OrderRepository repository) { // Spring injects the matching bean
this.repository = repository;
}
}

Note: DI is the tool, DIP is the goal. You can use DI and still break DIP, for example by injecting MySqlDatabase (a concrete class) instead of an interface.

Spotting a Violation

  • new inside business classes, such as new MySqlDatabase() or new EmailService().
  • Business code imports low-level packages like java.sql or an SDK for a payment provider.
  • Fields typed as concrete classes, not interfaces.
  • Unit tests need a real database or network.
  • Changing a library forces edits across many business classes.

Common Mistakes

1. An interface for everything. A small utility or a class that will never change (like a StringFormatter) does not need one. Use abstractions at boundaries that change: databases, external APIs, and message queues.

2. Putting the interface next to the detail. The interface should belong to the high-level code (what the business needs). If it lives in the database package and mirrors its methods, the business still depends on the detail.

3. Calling it "DIP" because you used a framework. Using Spring does not guarantee DIP. Check that your fields are interfaces.

SOLID Together

LetterPrincipleOne-line idea
SSingle ResponsibilityA class has one reason to change
OOpen/ClosedExtend with new code, do not modify old code
LLiskov SubstitutionA child can replace its parent safely
IInterface SegregationPrefer small interfaces over fat ones
DDependency InversionDepend on abstractions, not on details

DIP ties the others together: OCP needs interfaces to plug into, ISP keeps them small, and LSP makes sure the implementations behave.

Summary

  • DIP: high-level code and low-level details should both depend on abstractions.
  • The business layer owns the interface, and the details implement it.
  • The dependency is passed in (dependency injection), not created with new inside the class.
  • Benefits: swap details easily, test without real systems, and reuse business logic.
  • DIP is the principle, DI is the technique, and IoC containers like Spring do the wiring.
  • Warning signs: new in business code, concrete-class fields, and tests that need a real database.
  • Use abstractions at boundaries that change, not for every class.

Interview Answer: What Is the Dependency Inversion Principle?

The Dependency Inversion Principle says that high-level modules should not depend on low-level modules. Both should depend on abstractions. Business logic should not be tied to details like a specific database or email provider.

For example, if OrderService creates a MySqlDatabase with new, it is glued to MySQL, and testing it needs a real database. Instead, I define an OrderRepository interface that the business layer owns. A MySqlOrderRepository implements it, and I pass it into OrderService through the constructor. Now I can switch to PostgreSQL by adding a class, and I can test with a fake repository.

It is different from Dependency Injection, which is the technique of passing dependencies in, and from IoC, where a container like Spring creates and injects the objects. DI is how I achieve DIP. I use abstractions at the boundaries that are likely to change, like databases and external services, and not for every small class.

Next TopicSOLID Violations and How to Fix Them