HuntExams Academy logo
JavaIntermediate#design-patterns#spring

What is dependency injection?

Answer

Design pattern where dependencies are provided to a class rather than created inside it. Frameworks like Spring use constructor, setter, or field injection. Improves testability and decoupling.

Example

@Service
class OrderService {
  private final PaymentService paymentService;
  OrderService(PaymentService paymentService) { this.paymentService = paymentService; } // constructor injection
}

Detailed explanation

Dependency injection (DI) is a design pattern in which an object receives its dependencies from outside instead of constructing them internally. A dependency is another object that a class needs to perform its work, such as a payment service used by an order service. If OrderService creates a particular payment implementation with new, it controls that implementation's construction and becomes tightly coupled to it. With DI, OrderService declares what it needs, while a caller or container supplies the object. DI is one way to implement inversion of control (IoC).

In the example, OrderService receives PaymentService through its constructor and stores it in a private final field. The dependency is explicit, must be supplied during construction, and cannot later be reassigned. The @Service annotation makes OrderService eligible for Spring's component scanning. Assuming scanning includes this class, Spring registers its bean definition, resolves an appropriate PaymentService bean, and calls the constructor when creating OrderService. In modern Spring, a class with a single constructor does not need @Autowired on that constructor. PaymentService must also be registered as a bean; if several candidates exist, Spring may need @Qualifier or @Primary to resolve the choice.

Constructor injection is generally preferred for required dependencies because it supports fully initialised objects and straightforward unit tests. Setter injection supplies dependencies through methods after construction and can suit optional or reconfigurable collaborators, provided defaults and validation are clear. Field injection lets Spring populate annotated fields, commonly using reflection, but hides requirements from the constructor and makes ordinary manual construction less convenient. DI itself does not require Spring: new OrderService(paymentService) is also dependency injection.

The main benefits are separating business logic from object assembly and making collaborators easier to replace in tests. A unit test can pass a fake or mock PaymentService without starting Spring. Depending on an interface can further reduce coupling to concrete implementations, although interfaces are not mandatory for DI. Interviewers usually expect you to explain who creates and supplies dependencies, justify constructor injection, and distinguish dependency injection from simply adding framework annotations.

Key points to remember

  • DI supplies collaborators externally instead of creating them inside the consuming class.
  • DI implements inversion of control and works with or without a framework.
  • Constructor injection makes required dependencies explicit and supports final fields.
  • Spring resolves dependencies from registered beans; ambiguous candidates may require @Qualifier or @Primary.
  • Injected collaborators can be replaced with mocks or fakes in isolated unit tests.

Common mistakes candidates make

  • Claiming that dependency injection requires Spring, @Autowired, or interfaces.
  • Using field injection by default without recognising its hidden dependencies and testing drawbacks.
  • Assuming @Service alone guarantees successful wiring when dependencies may be missing or ambiguous.

Likely follow-up questions

How is dependency injection different from inversion of control?

Inversion of control is the broader principle of transferring control from application code to an external mechanism. Dependency injection applies that principle specifically to supplying an object's collaborators.

Why is constructor injection usually preferred over field injection?

Constructor injection exposes required dependencies and allows them to be stored in final fields. Tests can construct the class directly with mocks or fakes, without reflection or a Spring container.

What happens if two beans implement PaymentService?

If Spring cannot select a unique candidate, dependency resolution fails, typically with a NoUniqueBeanDefinitionException. Use @Qualifier to identify the intended bean or @Primary to establish a preferred candidate.

Interview tip: Define DI first, explain how the OrderService constructor receives PaymentService, and finish with why this makes unit testing easier.

Related Interview Questions

1
JavaBeginner#exceptions

What is the difference between throw and throws?

Open
2
JavaIntermediate#exceptions

Explain try-with-resources.

Open
3
JavaIntermediate#fundamentals

What is autoboxing and unboxing?

Open