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

Introduction: The Problem of Tight Coupling in Java

\n\n

Imagine you are writing an application for sending notifications. In your NotificationService class, an EmailSender object is created directly. Everything works perfectly until one day the client asks you to add support for SMS and Push notifications. You will have to rewrite NotificationService, change constructors, and add new conditions. The code becomes fragile, difficult to test and extend.

\n\n

This is the classic problem of tight coupling. The solution is called Dependency Injection — one of the key design principles in Java and the foundation of frameworks like Spring. In this article, we will break down what Dependency Injection is in simple terms, how to implement it, and why no professional Java developer can do without it.

\n\n

What is Dependency Injection in Simple Terms?

\n\n

Dependency Injection (DI) is a design pattern in which an object receives its dependencies from the outside, rather than creating them itself. Instead of writing new EmailSender() inside a class, you "inject" a ready-made object through a constructor, setter, or interface.

\n\n

DI is closely related to the principle of Inversion of Control (IoC). If previously your class itself managed the creation of dependencies, now control is transferred to an external container (e.g., Spring IoC).

\n\n

Main advantages of DI:

\n
    \n
  • Loose coupling — classes depend on abstractions, not concrete implementations.
  • \n
  • Simplified testing — dependencies are easily replaced with mocks or stubs.
  • \n
  • Code reusability — the same service can be used in different contexts.
  • \n
  • Ease of maintenance — changing an implementation does not require modifying consumer classes.
  • \n
\n\n

Types of Dependency Injection in Java

\n\n

There are three main ways to implement Dependency Injection in Java. Let's look at each with an example.

\n\n

1. Constructor Injection

\n\n

The most common and recommended method. Dependencies are passed through constructor parameters and stored as class fields (usually final).

\n\n
// Interface for sending messages\npublic interface MessageSender {\n    void send(String message, String recipient);\n}\n\n// Concrete implementation — Email\npublic class EmailSender implements MessageSender {\n    @Override\n    public void send(String message, String recipient) {\n        System.out.println("Sending Email: " + message + " to " + recipient);\n    }\n}\n\n// Service that uses DI via constructor\npublic class NotificationService {\n    private final MessageSender sender;\n\n    // Dependency is injected through the constructor\n    public NotificationService(MessageSender sender) {\n        this.sender = sender;\n    }\n\n    public void notifyUser(String message, String userEmail) {\n        sender.send(message, userEmail);\n    }\n}\n\n// Usage\npublic class Main {\n    public static void main(String[] args) {\n        MessageSender emailSender = new EmailSender();\n        NotificationService service = new NotificationService(emailSender);\n        service.notifyUser("Welcome!", "user@example.com");\n    }\n}
\n\n

Pros: the object is created in a fully ready state, dependencies are immutable, easy to test.

\n\n

2. Setter Injection

\n\n

Dependencies are passed through setter methods after the object is created. It is more often used for optional dependencies.

\n\n
public class NotificationService {\n    private MessageSender sender;\n\n    // Setter for dependency injection\n    public void setSende                

Blogs

Book Recommendations