[Best practice] Use Constructor-Based Dependency Injection with Spring

Old habits die hard, but die they must
  FR
 4 min. to read
 chop
 No comment yet
  (last mod.: 2021-04-23)

If you’ve been using Spring for a while, or copy-pasting some web tutorials or examples, you’ve probably put some @Autowired annotations on private fields. This does work, but it’s not the best way to do it.

TL;WR #

Don’t use @Autowired, create a constructor that takes all required fields as parameters.

If your class needs several constructors, annotate the one Spring should use with @Autowired.

Spring Team’s Recommendation #

If you have an Ultimate license of IntelliJ IDEA, if you annotate a private field with @Autowired, a warning should appear. Expanding it, you should see the following:

Field injection is not recommended
Inspection info: Spring Team recommends: “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.

Using Constructor-Based Dependency Injection #

Annotation-Based Configuration #

Imagine a HelloService that needs Spring to inject a HelloRepository and a property from the application.properties file. The way to do so is to add a constructor that will take these parameters instead of annotating the private fields.

 1@Service
 2public class HelloService {
 3
 4    private HelloRepository repository;
 5    private String fromName;
 6
 7    public HelloService(
 8        HelloRepository repository,
 9        @Value("${kp.hello.from.name}") String fromName
10    ) {
11        this.repository = repository;
12        this.fromName = fromName;
13    }
14
15    // Logic here
16}

From Spring 4.3, the @Autowired can be omitted for classes with only one constructor. With previous versions or classes that have more than one constructor, the annotation will indicate Spring which constructor to use.

Behind the curtains, when Spring runs, it discovers the HelloService via the @Service annotation and know it must be loaded. Then it will discover that there is a constructor whose parameters it will try to supply. It will search in the context for those and will try to pass those. Expect an exception if one of those is not available.

XML-Based Configuration #

XML remains a valid way to configure Spring (I had a hard time quitting it, actually), and it still works for constructor-based injection. For instance:

 1<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 2    <property name="location" value="application.properties"/>
 3</bean>
 4
 5<!-- ... -->
 6
 7<bean id="helloService" class="org.keyboardplaying.demo.HelloService">
 8    <constructor-arg index="0" ref="helloRepository"/>
 9    <constructor-arg index="1" value="${kp.hello.from.name}"/>
10</bean>

There, no discovery is left to Spring, as everything is explicitly configured.

Pros & Cons #

Pro: Testability #

What if we wrote our HelloService the “traditional” way?

 1@Service
 2public class HelloService {
 3
 4    @Autowired
 5    private HelloRepository repository;
 6
 7    @Value("${kp.hello.from.name}")
 8    private String fromName;
 9
10    // Logic here
11}

Now imagine you want to unit test this. First, you’d have no solution to set those fields except using the reflection API, which completely breaks encapsulation.

Another con of field injection would be that you can’t ensure the bean has been fully initialized, which may result in random NullPointerException as your code would try to call a dependency that would not have been set.

Pro: OOP Paradigm #

From the Object-Oriented Programming point of view, using constructor is more natural to initialize objects.

Pro/con: Verbosity #

Complex services may require many dependencies. Associated constructors can become quite cumbersome.

But, as Baeldung highlighted, this may entice you to be more cautious about the number of dependencies of your service. Maybe write more specialized components and benefit from intelligent composition.

My Going Further #

Making Beans Immutable #

Since all dependencies of a service are set at the time of instantiation, why not make them final? This prevents any other component to change it at runtime, due to a security intrusion or a programming mistake for instance.

Not Writing the Constructor #

I’ve not yet had time to write about Project Lombok, but it shares one great purpose with Spring Boot: avoid boilerplate code.

Why spend time writing constructors, or updating them when the dependencies of a component change? Lombok uses reflection to generate these at compilation time.

For instance, you could do the following:

1@Service
2@AllArgsConstructor
3public class HelloService {
4
5    private HelloRepository repository;
6
7    // Logic here
8}

Or, making the service immutable, we could obtain:

1@Service
2@RequiredArgsConstructor
3public class HelloService {
4
5    private final HelloRepository repository;
6
7    // Logic here
8}

You can have a look at Lombok’s documentation to see the difference between the @AllArgsConstructor and @RequiredArgsConstructor.

There is, nonetheless, one big restriction to this use of Lombok: this cannot be used for constructors that require annotated parameters (such as parameters annotated with @Value). In my experience, however, most Spring beans can be initialized this way.

Works with Quarkus, Too #

Quarkus’s simplified dependency injection, ArC, is not a full CDI implementation. However, it brings the possibility of using constructor injection.

I recently took advantage of it, instead of using @Inject, which is actually quite similar to @Autowired.

Conclusion #

Having other thoughts worthy of note about the use of constructor-based dependency injection? Please, share with us.