Why Spring’s @Autowired Is Problematic and How Constructor Injection Solves It
This article explains why overusing Spring's @Autowired can lead to hidden dependencies, tight coupling, NullPointerExceptions, and testing difficulties, and demonstrates how constructor injection provides clearer, safer, and more test‑friendly dependency management.
Introduction
Many newcomers love Spring's @Autowired for its simplicity, but in larger projects it can cause hidden problems.
Since Spring 4.0 the framework recommends constructor injection over blind use of @Autowired.
1. Implicit dependencies hide relationships
Field injection hides the true dependency graph, making the code harder to understand and maintain.
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}Using constructor injection makes dependencies explicit:
@Service
public class MyService {
private final MyRepository myRepository;
// constructor injection
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}Dependencies are clear.
Testing becomes easier because mocks can be passed via the constructor.
2. Strong coupling to concrete implementations
Injecting a concrete class ties the service to that implementation, violating the principle of programming to an interface.
@Service
public class MyService {
@Autowired
private SpecificRepository specificRepository;
}Inject an interface instead and configure the concrete bean separately:
@Service
public class MyService {
private final Repository repository;
public MyService(Repository repository) {
this.repository = repository;
}
} @Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}Implementation can be swapped without changing service code.
Aligns with interface‑driven design.
3. Risk of NullPointerException
If the container has not injected the field before the bean is used, a NullPointerException occurs.
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public void doSomething() {
myRepository.save(); // may throw NPE
}
}Constructor injection guarantees that the dependency is non‑null at object creation:
@Service
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public void doSomething() {
myRepository.save();
}
}4. Ambiguous autowiring when multiple candidates exist
When several beans implement the same interface, plain @Autowired leads to errors.
Explicit configuration (e.g., @Primary or @Qualifier) solves the problem but adds complexity.
Constructor injection combined with explicit bean definition keeps the code clean.
@Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}5. Unit testing becomes painful
Field injection prevents easy injection of mock objects, making tests hard to write.
public class MyServiceTest {
@Test
public void testDoSomething() {
MyRepository mockRepository = mock(MyRepository.class);
MyService myService = new MyService(mockRepository);
// test logic
}
}Conclusion
Constructor injection provides clear dependencies, reduces coupling, avoids NPEs, simplifies bean selection, and makes unit testing straightforward. @Autowired can still be used in limited scenarios, but the recommended practice is to prefer constructor injection.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
