Dependency Injection in Java: A Complete Guide for Beginners

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What is Dependency Injection in Java and why is it needed?



Imagine you are building a house. You need bricks, cement, and tools. If you have to mine the clay, fire the bricks, and mix the cement yourself every time, the construction will take forever. It's much easier when the materials are already ready and delivered to the construction site. This is exactly how Dependency Injection (DI) works in Java — it's a design pattern where an object receives ready-made dependencies from the outside, rather than creating them itself.



In simple terms: Dependency Injection is when one object passes all the necessary dependencies to another object. Instead of a class creating its own dependencies, it receives them through a constructor, setter, or interface.



Why is Dependency Injection so important?



  • Reduced code coupling: Classes stop being rigidly dependent on specific implementations.
  • Simplified testing: You can easily replace real objects with mocks in unit tests.
  • Increased reusability: The same class can be used in different contexts.
  • Improved readability: Code becomes clearer, and dependencies become explicit.


The problem without Dependency Injection: the "Tight Coupling" anti-pattern



Let's look at a typical example of code without using DI. Suppose we have a class EmailService that sends emails, and a class NotificationService that uses EmailService.



// Class without DI — bad examplepublic class EmailService {    public void sendEmail(String message) {        System.out.println("Sending email: " + message);    }}

public class NotificationService { private EmailService emailService;

public NotificationService() { // Problem: creating the dependency directly inside the class this.emailService = new EmailService(); }

public void sendNotification(String message) { emailService.sendEmail(message); }}


What is the problem with this code?



  • Tight coupling: NotificationService is rigidly tied to EmailService. If you want to send SMS instead of email, you have to rewrite the class.
  • Testing difficulty: You cannot replace EmailService with a test stub (mock).
  • Violation of the Single Responsibility Principle: The NotificationService class is responsible not only for notifications but also for creating the sending service.


How Dependency Injection works: three ways of injection



In Java, there are three main ways to implement dependency injection. Let's look at each with code examples.



1. Constructor Injection



This is the most common and recommended method. Dependencies are passed through constructor parameters.



// Interface for flexibilitypublic interface MessageService {    void sendMessage(String message);}

// Implementation for emailpublic class EmailService implements MessageService { @Override public void sendMessage(String message) { System.out.println("Email sent: " + message); }}

// Implementation for SMSpublic class SmsService implements MessageService { @Override public void sendMessage(String message) { System.out.println("SMS sent: " + message); }}

// Class with DI via constructorpublic class NotificationService { private final MessageService messageService;

// Injecting dependency through constructor public NotificationService(MessageService messageService) { this.messageService = messageSe

Blogs

Book Recommendations