Skip to main content

Why can incorrect DI lead to "dependency hell"?

"Dependency hell" when using DI (Dependency Injection) is a situation where there are so many dependencies, and they are so tangled, that the system stops being manageable. The cause is not DI itself, but its incorrect application.

Here are the key mechanisms behind how this happens.


1. Excessive dependencies (God objects and 10-argument constructors)

When a developer mindlessly "injects everything in sight", classes start depending on dozens of others:

java
public OrderService(UserRepo u, Payment p, Logger l, Analytics a, DiscountEngine d, Mailer m, Cache c, Config cfg) { ... }

What happens:

  • A single class knows about too many details of the system.
  • The smallest change to one dependency requires reworking the whole object tree.
  • Unit tests become bulky: dozens of components have to be mocked.

Conclusion: DI does not replace architecture, it just highlights that the architecture is bad.


2. Circular dependencies

When two classes depend on each other, directly or through a chain of intermediaries:

java
class UserService { UserRepo repo; } class UserRepo { UserService service; }

What happens:

  • The container cannot build the dependency graph (an error at startup).
  • Even if it is worked around, the logic becomes fragile and unpredictable.
  • Any attempt to change one module breaks the other.

Conclusion: cycles in DI are almost always a sign of a design mistake.


3. "Container magic" and lost transparency

When the container creates and injects dependencies by annotations, but the relationships are not obvious. The developer no longer understands:

  • who creates the object,
  • what its lifecycle is,
  • which implementation is plugged in.

As a result:

  • errors only show up at runtime,
  • the project becomes unpredictable,
  • a new dependency or a class rename breaks half the system.

Conclusion: DI should be explicit, not magical. Transparency matters more than convenience.


4. Incorrect lifecycle management of dependencies (scopes)

When dependencies with different "lifecycles" get mixed: for example, a Singleton depends on a RequestScoped object. What happens:

  • The object lives longer than its dependency.
  • The container creates new instances at unpredictable moments.
  • Memory leaks and data desynchronization appear.

Conclusion: scope is part of the architecture, not just a container setting.


5. Over-complicated configuration

When DI turns into a "mini-language", and the project into a pile of YAML, XML, and annotations. The result:

  • any small setting requires digging through dozens of files,
  • it is hard to tell where a specific implementation comes from,
  • adding a new dependency without breaking the old ones becomes impossible.

Conclusion: good DI is minimalist; bad DI turns the architecture into a maze.


Conclusion

Incorrect DI does not lead to modularity, it leads to chaos. The container loses control, and dependencies start running the system instead of the other way around.

The main idea: DI should reduce coupling, not spread chaos. When injection turns into automatically "injecting everything that moves", the architecture degrades, and the project slides into dependency hell.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.