Browse Java Design Patterns & Enterprise Application Architecture

Implementing Dependency Injection in Java

Implement dependency injection in Java with explicit constructor wiring first, then add containers only when the application boundary truly benefits.

Dependency injection: A design style where an object receives its collaborators from the outside instead of constructing or locating them itself.

The simplest useful version of dependency injection in Java does not start with Spring or Guice. It starts with constructor parameters.

Start With Explicit Constructor Injection

 1public final class InvoiceService {
 2    private final TaxCalculator taxCalculator;
 3    private final InvoiceRepository invoiceRepository;
 4
 5    public InvoiceService(TaxCalculator taxCalculator,
 6                          InvoiceRepository invoiceRepository) {
 7        this.taxCalculator = taxCalculator;
 8        this.invoiceRepository = invoiceRepository;
 9    }
10}

This design is valuable because the dependency graph is visible:

  • the service does not decide its own collaborators
  • tests can supply fakes or stubs directly
  • the application bootstrap layer owns wiring decisions

The visual below shows the boundary:

    flowchart LR
	    Bootstrap["Bootstrap / container"] --> Tax["TaxCalculator"]
	    Bootstrap --> Repo["InvoiceRepository"]
	    Tax --> Service["InvoiceService"]
	    Repo --> Service

Why This Is Better Than Hidden Construction

If InvoiceService internally creates its own repository or calculator, it quietly takes ownership of configuration, lifecycle, and implementation choice. That makes the service harder to test and harder to adapt.

Dependency injection separates:

  • behavior inside the service
  • wiring at the application edge

That separation is the real benefit, not the framework annotation.

Constructor, Setter, Or Field Injection?

Constructor injection is usually the best default when dependencies are required.

Setter injection can make sense for optional collaborators or post-construction configuration, but it weakens immutability and makes incomplete objects easier to create accidentally.

Field injection is convenient in frameworks, but it hides dependencies and makes plain construction harder. In codebases that care about test clarity and explicit design, it is usually the weakest option.

Containers Should Clarify, Not Replace, Design

IoC containers become useful when object graphs are large enough that manual composition gets noisy. But the container should still reflect a clean design, not rescue a confused one.

A good DI-aware Java system still makes it easy to answer:

  • what this class depends on
  • who owns creation
  • what lifecycle each dependency has
  • which dependencies are optional versus required

Design Review Questions

When reviewing dependency injection in Java, ask:

  • Are required collaborators injected explicitly?
  • Does the class avoid constructing or locating its own dependencies?
  • Would constructor injection be clearer than the current style?
  • Is the container supporting the design, or hiding it?

The best DI code is boring in the right way. It makes wiring visible and lets the business class focus on its job.

Loading quiz…
Revised on Thursday, April 23, 2026