Dependency Injection (concepts & Spring basics)
Dependency injection is the technique of giving an object the collaborators it needs from the outside — through its constructor — instead of having it create them itself, which decouples your code and makes it far easier to test.
Learn Dependency Injection (concepts & Spring basics) in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice…
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Before You Start
You should be comfortable with interfaces (DI depends on programming to abstractions) and constructors . A look at lambdas helps for the functional-interface examples.
What You'll Learn
The Big Idea — Power Sockets, Not Soldered Wires
💡 Analogy: A class that new s its own dependencies is like a lamp with its power cable soldered straight into the wall — it works, but you can never move it, test it on a bench, or swap the bulb's power source. Dependency injection gives the lamp a plug and the wall a socket : the lamp just declares "I need power" and whoever installs it decides what to plug in — mains power, a battery pack, or a test rig. The lamp doesn't change; only what you plug in does.
"Inversion of Control" is simply this idea: the lamp no longer controls where its power comes from — an outside installer does.
1️⃣ The Problem — Hard-Wired Dependencies
When a class builds its own collaborators with new , it is welded to that exact implementation. You can't swap EmailService for an SMS one, and you certainly can't test the class without triggering the real thing. That is tight coupling .
2️⃣ The Fix — Constructor Injection
Depend on an interface and receive the implementation through the constructor. Now the same OrderService works with an email notifier, an SMS notifier, or anything else — and the caller (a manual "injector") decides which to plug in.
3️⃣ The Payoff — Testability
Because the dependency is an interface passed in, a test can inject a fake that records calls instead of doing real work. You can then assert exactly what the class did — with no email server, no network, no flakiness.
4️⃣ How Spring Automates the Wiring
In a small program you do the wiring in main . In a large app that quickly becomes a tangle, so frameworks like Spring provide a container that wires everything for you. You annotate classes as beans with @Component / @Service , and Spring constructs the object graph and injects dependencies — often just by spotting the constructor (so @Autowired is optional on a single constructor).
🧠 Quick Recall
Answer: control over creating and wiring collaborators. Instead of the class deciding what to new , an outside party (your code or a container) decides and supplies it.
Answer: so any implementation — real or fake — can be injected. Depending on the concrete class re-couples you to it and blocks substitution.
Answer: because it's set once in the constructor and never reassigned. final documents that, prevents accidental rebinding, and guarantees the object is fully initialised.
🎯 YOUR TURN — Inject a Logger
Make Calculator depend on the Logger interface via constructor injection, store it final , and log each addition.
🧩 MINI-CHALLENGE — Two injected collaborators
Build a CheckoutService that takes a PaymentGateway and a Receipts recorder, wired with lambdas in main .
Common Errors
- ❌ new-ing dependencies inside the class. It welds you to one implementation. Fix: accept the dependency through the constructor.
- ❌ Depending on a concrete class, not an interface. Substitution becomes impossible. Fix: program to an interface.
- ❌ Field injection everywhere. Hides dependencies and breaks plain-Java tests. Fix: prefer constructor injection.
- ❌ Too many constructor parameters. Often a sign a class does too much. Fix: split responsibilities or group related deps.
- ❌ Thinking you need a framework. DI is a design idea. Fix: wire by hand in small apps; reach for Spring at scale.
- ❌ Service locators / global singletons. They hide dependencies and reintroduce coupling. Fix: pass dependencies in explicitly.
Pro Tips
- 💡 Constructor-inject and mark fields final. Explicit, immutable, always-valid objects.
- 💡 One-method interfaces are functional — you can inject a lambda directly as a quick fake or real impl.
- 💡 In Spring, a single constructor needs no @Autowired — the container infers it.
- 💡 Keep wiring at the edges (main, a config class, or the container) so your business classes stay free of construction logic.
📋 Quick Reference
Concept
Syntax / idea
Note
tight coupling
new EmailService()
Avoid inside business classes
abstraction
interface Notifier
Depend on this
constructor injection
Svc(Notifier n)
Preferred form
final field
private final Notifier n;
Set once, never null
manual injector
new Svc(new Email())
Wiring in main
test double
new Svc(fake)
No real I/O
IoC
control moves outward
DI implements it
Spring bean
@Component / @Service
Container-managed
Spring inject
@Autowired
Optional on 1 ctor
benefit
decoupling + testability
The whole point
avoid
field injection, singletons
Hide dependencies
📚 Resources / Keep Learning
- 📘 FunctionalInterface — Java 21 API (docs.oracle.com)
- 📗 Using Interfaces — dev.java tutorial
- 📗 dev.java — official tutorials and guides
❓ Frequently Asked Questions
🎉 Lesson Complete!
You now understand Inversion of Control, why hard-wired new dependencies hurt, how to apply constructor injection against an interface in plain Java, how that unlocks easy testing with fakes, and how Spring's @Component / @Autowired automate the same idea at scale.
Next up: Serialization — turning objects into bytes (and back), with its serialVersionUID, transient fields, and security caveats.
Practice quiz
What is dependency injection?
- Creating dependencies inside a class with new
- Injecting code at runtime via reflection only
- Supplying a class's dependencies from the outside
- A type of inheritance
Answer: Supplying a class's dependencies from the outside. DI means a class receives the objects it needs (its dependencies) from outside rather than constructing them itself.
What is Inversion of Control (IoC)?
- Control of object creation/wiring moves out of the class to a caller or container
- The class controls everything
- Reversing a list
- Running code backwards
Answer: Control of object creation/wiring moves out of the class to a caller or container. IoC flips who is in charge: instead of a class creating its collaborators, an external party provides them. DI is one way to achieve IoC.
Which is the preferred form of injection?
- Field injection
- Static injection
- No injection
- Constructor injection
Answer: Constructor injection. Constructor injection makes dependencies explicit and lets you mark them final, ensuring the object is fully and validly built.
Why does DI improve testability?
- It makes code faster
- You can inject fakes/mocks instead of real collaborators
- It removes all bugs
- It hides dependencies
Answer: You can inject fakes/mocks instead of real collaborators. Because a class depends on an abstraction passed in, tests can supply a fake implementation and assert on interactions — no real I/O.
DI works best when classes depend on...
- interfaces / abstractions
- concrete classes
- static methods
- global variables
Answer: interfaces / abstractions. Depending on an interface lets any implementation (real or fake) be injected, which is the core of decoupling.
In plain Java, who plays the role of the 'injector'?
- The JVM
- The garbage collector
- The code that constructs and wires the objects (e.g. main or a factory)
- Nobody
Answer: The code that constructs and wires the objects (e.g. main or a factory). Without a framework, your own wiring code (in main or a factory) creates implementations and passes them into constructors.
What does Spring's @Component annotation do?
- Deletes a class
- Marks a class as a bean for Spring to manage and inject
- Makes a class final
- Starts a thread
Answer: Marks a class as a bean for Spring to manage and inject. @Component (and @Service, @Repository) registers the class in Spring's container so it can be created and injected automatically.
What does @Autowired tell Spring to do?
- Run the method twice
- Log the call
- Cache the result
- Inject a matching bean into the field/constructor
Answer: Inject a matching bean into the field/constructor. @Autowired asks Spring to supply a managed bean of the required type; on a constructor it's often optional in modern Spring.
A benefit of decoupling via DI is...
- Faster compilation only
- You can swap implementations without changing the dependent class
- Smaller JARs
- No need for interfaces
Answer: You can swap implementations without changing the dependent class. Because the class only knows the interface, you can swap Email for SMS, or real for fake, without touching it.
Constructor injection lets you mark dependencies as...
- static
- volatile
- final
- transient
Answer: final. Dependencies assigned once in the constructor can be final, signalling they never change after construction.
Continue this course
- Previous: The Builder Pattern
- Next: Serialization