Spring Boot DI and the Dependency Inversion Principle
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Spring Boot helps apply DIP by handling construction and wiring, but it does not enforce the principle. Suppose order processing needs to charge a payment. The application can define a small PaymentGateway contract, the Stripe adapter can implement it, and Spring configuration can inject that adapter into OrderService. The service source code then depends on the application contract rather than the Stripe SDK.
That is different from simply adding @Service and constructor-injecting a concrete StripeClient; that is DI, but the high-level service still names the low-level detail. Spring also needs an unambiguous bean selection when several implementations exist. The design creates the inversion; the container automates assembly.
Spring Boot DI and the Dependency Inversion Principle
Direct Answer
Spring Boot provides an IoC container and dependency injection, which can support DIP but does not enforce it. A service follows DIP when its source dependency points to a suitable abstraction owned around the policy it needs; a low-level adapter implements that contract. Spring can select and inject the adapter, but it can also inject a concrete class in a tightly coupled design.
Code Example
// Interface defined in service layer (high-level module owns this)
public interface UserRepository {
Optional<User> findById(Long id);
void save(User user);
}
// Implementation in repository layer (low-level module implements interface)
@Repository
public class JpaUserRepository implements UserRepository {
public Optional<User> findById(Long id) {
// JPA logic to query database
return Optional.of(new User(id, "Alice"));
}
public void save(User user) { /* persist to DB */ }
}
// High-level service depends on interface only
@Service
public class UserService {
private final UserRepository userRepository; // interface type, not JpaUserRepository
// Constructor injection - Spring provides the implementation
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(Long id) {
var user = userRepository.findById(id).orElseThrow();
// business logic here
userRepository.save(user);
return user; // UserService never knows about JpaUserRepository
}
}
Common Mistakes & Pitfalls
- Saying Spring enforces DIP: it automates DI and can wire tightly coupled designs too.
- Equating interface injection with inversion: contract ownership and dependency direction matter.
- Expecting automatic bean choice: several implementations need an explicit resolution rule.
- Putting framework annotations into core policy without considering the boundary: this can create a framework dependency.
- Creating an interface for every bean: use abstractions where they protect a meaningful policy boundary.
Check your understanding
Answer these without looking back at the article.
- What happens if Spring finds two implementations of the same interface?
- How do you use @Qualifier in Spring?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.