On this page

Inversion of Control (IoC)

What is control in Java?

Control means who decides what happens in a program:

  • which objects get created,
  • when they get created,
  • and which methods get called.

In a normal Java program, your code controls everything. You create objects with new and call their methods yourself:

public class OrderService {
private EmailService emailService = new EmailService(); // OrderService decides
public void placeOrder(String customer, String product) {
emailService.sendEmail(customer, "Order confirmed: " + product);
}
}

Here, OrderService is in control of its dependency. It decides which class to use (EmailService) and when to create it.

What is Inversion of Control (IoC)?

Inversion of Control (IoC) means handing control to someone else, instead of your code doing everything itself.

"Inversion" just means "flipping". Normally, your code is in charge. With IoC, the control flips: something outside your code (usually a framework) takes charge, and your code just does its part when asked.

text
1Normal control: Your code → creates objects, calls methods, decides the flow
2Inverted: Framework → creates objects, calls your code, decides the flow

Think of the difference between driving your own car and taking a taxi:

  • Driving yourself: you control everything: the route, the turns, the speed.
  • Taking a taxi: you just say where you want to go. The driver handles the rest.

With IoC, your code is the passenger. It says what it needs, and the framework takes care of how and when.

You've probably used IoC already without noticing it. In a JUnit test, you never call your test methods yourself:

class OrderServiceTest {
@Test
void placeOrderSendsConfirmation() {
// JUnit calls this method. You never do.
}
}

You just write the method and mark it with @Test. JUnit decides when to create the test class and when to run each method. That's control handed to someone else.

IoC is often summed up as:

"Don't call us, we'll call you."

In this page, we're interested in one specific kind of control: who creates a class's dependencies. Let's see how that control gets inverted.

How DI changes control flow

With Dependency Injection, OrderService no longer creates its dependency. It only receives one:

public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService; // someone else decided
}
}
// The outside code now decides
OrderService orderService = new OrderService(new SmsNotificationService());

The control has moved:

text
1Before DI: OrderService decides which notification class to create
2After DI: Outside code decides, OrderService just uses what it gets

Control used to flow from inside the class. Now it comes from outside. This flip is called Inversion of Control.

IoC vs DI

Inversion of Control (IoC) is the general idea: give control to someone else instead of keeping it in your code.

A common way to describe it:

"Don't call us, we'll call you."

IoC appears in many places, not just with dependencies:

ExampleWho has control
JUnit runs your @Test methodsJUnit decides when to call them
A button click runs your listenerThe UI framework decides when to call it
Spring creates your objectsSpring decides when to create and connect them

Dependency Injection is one specific way to do IoC: the control of creating dependencies is moved outside the class.

IoCDI
What it isA general ideaA specific technique
What is invertedAny control (calls, flow, creation)Creating and providing dependencies
RelationshipThe goalOne way to reach the goal

In short: IoC is the idea, DI is one way to do it.

Dependency Inversion Principle (DIP) vs Dependency Injection

Both start with "Dependency" and have similar names, so they're often mixed up. They answer different questions.

Dependency Inversion Principle (DIP) is the D in SOLID:

Important classes should depend on interfaces, not on specific classes.

It's about what you depend on.

Dependency Injection is about how you get it: passed in from outside.

You can do one without the other.

DI without DIP: injected, but still depends on a specific class:

public class OrderService {
private final EmailService emailService; // specific class
public OrderService(EmailService emailService) { // but injected
this.emailService = emailService;
}
}

You can't switch to SMS, because the type is EmailService.

DIP without DI: depends on an interface, but still creates it:

public class OrderService {
private final NotificationService notificationService =
new EmailNotificationService(); // interface type, but created inside
}

You still can't switch without editing OrderService.

Both together: depends on an interface, and receives it from outside:

public class OrderService {
private final NotificationService notificationService; // DIP: interface
public OrderService(NotificationService notificationService) { // DI: injected
this.notificationService = notificationService;
}
}
DIPDI
Question it answersWhat should I depend on?How do I get it?
AnswerAn interfacePassed in from outside
TypeDesign principle (SOLID)Technique

Use them together to get the full benefit.

Why manually managing dependencies becomes difficult

With DI, classes no longer create their dependencies. But someone still has to create every object and connect them:

public class Main {
public static void main(String[] args) {
MailConfig mailConfig = new MailConfig("smtp.example.com", 587);
SmtpClient smtpClient = new SmtpClient(mailConfig);
NotificationService notificationService = new EmailNotificationService(smtpClient);
DatabaseConfig dbConfig = new DatabaseConfig("jdbc:postgresql://localhost/shop");
OrderRepository orderRepository = new OrderRepository(dbConfig);
PaymentService paymentService = new PaymentService();
OrderService orderService =
new OrderService(notificationService, paymentService, orderRepository);
}
}

That's for one service. As the app grows, this becomes hard:

Order matters. SmtpClient must be created before EmailNotificationService, which must be created before OrderService. Get the order wrong, and it won't compile.

Sharing is manual. If five services need the same OrderRepository, you must create it once and pass the same object to all five yourself.

Changes spread. If EmailNotificationService needs a new dependency, every place that creates it must change.

It grows huge. Real apps have hundreds of classes. This wiring code can grow to hundreds of lines that do nothing but create and connect objects.

Cleanup is manual. Objects like database connections must be closed properly when the app stops.

What we need is something that takes over this job: reads what each class needs, creates objects in the right order, shares them, and cleans them up.

That's an IoC container, and in Spring, it's called the ApplicationContext. That's what we'll learn next.

Next TopicSpring IoC Container & Beans