Today we'll go over how Spring automatically wires dependencies using the autowiring mechanism — autowiring. Get ready — there's a lot of "magic" ahead (spoiler: it's really just solid architecture).
What is autowiring?
Imagine this scenario: your app has a Car class that needs an Engine object. Before, we wired such dependencies manually via constructors, setter methods, or fields. But Spring provides an automatic wiring mechanism called autowiring. It's like ordering a dish at a cafe and the waiter makes sure the right ingredients end up on your plate. Handy, right?
Autowiring lets the Spring IoC container automatically find and wire beans based on their type or name. That removes some of the boring setup work. The main tool here is the @Autowired annotation.
Example:
@Component
public class Car {
private Engine engine;
// Automatic wiring via constructor
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
@Component
public class Engine {
// Empty class for example
}
In this case Spring will automatically create an Engine bean (if it's in the context) and inject it into Car.
Autowiring strategies
Spring uses a few strategies for autowiring. Let's dig into them.
By type (
byType)Spring searches for a bean of the required type in the context. For example, if an
Engineis required, it looks for a bean of that type.@Autowired private Engine engine; // Searches for a bean of type EngineBy name (
byName)If there are multiple beans of the same type in the context, Spring can use the name for wiring. For that we use the
@Qualifierannotation, which we'll talk about in a bit.
Ambiguity problems: what to do if there are multiple candidates?
Suppose we have two beans of the same type:
@Component
public class DieselEngine implements Engine {
}
@Component
public class ElectricEngine implements Engine {
}
Now the Car needs an Engine. What will Spring do? It'll get confused. Just like you standing in front of a shelf full of identical chargers. In such cases Spring throws: NoUniqueBeanDefinitionException.
But don't worry: Spring gives us tools to resolve this.
The @Qualifier annotation
To specify a particular bean we use @Qualifier. It's like a first name on top of a shared last name — when two people share the same surname, the first name differentiates them.
Example:
@Component
public class Car {
private Engine engine;
@Autowired
public Car(@Qualifier("dieselEngine") Engine engine) {
this.engine = engine;
}
}
Here we explicitly said we want the bean named dieselEngine.
How does Spring know the bean name? By default the bean name matches the class name with a lowercase first letter (e.g. dieselEngine). If you need a custom name you can use @Component("customName").
The @Primary annotation
If you have multiple beans of the same type and want one to be used "by default", you can mark it with @Primary.
Example:
@Component
@Primary
public class ElectricEngine implements Engine {
}
@Component
public class DieselEngine implements Engine {
}
Now Spring will use ElectricEngine by default, unless you explicitly pick another bean with @Qualifier.
Autowiring via fields, setters, and constructors
We already mentioned dependencies can be injected via constructor, setter, or directly into a field. Let's see how that plays with @Autowired.
Via constructor
This is the preferred way because it makes objects immutable, which is great for large systems.
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
Note: if a class has only one constructor, you can omit @Autowired. Spring will detect and use that constructor automatically.
Via setter
This way is more flexible since it allows changing dependencies at runtime, though that's rarely needed.
@Component
public class Car {
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
Via field
The most "magical" way, but also the most controversial because — you guessed it — it breaks encapsulation.
@Component
public class Car {
@Autowired
private Engine engine;
}
Avoid this approach, especially in large projects.
4. Common mistakes and how to fix them
Issue 1: No matching bean
If Spring can't find a bean to inject, it throws NoSuchBeanDefinitionException. Make sure the bean is defined, e.g. with @Component or @Bean.
Issue 2: Multiple beans of the same type
As we discussed, Spring will throw NoUniqueBeanDefinitionException in such cases. You can resolve this by adding @Qualifier or @Primary.
5. Practical example
Let's put everything together in one example. We'll build an app where there's a car that needs an engine and a gearbox.
// Engine interface
public interface Engine {
String getType();
}
// Petrol engine implementation
@Component
@Qualifier("petrolEngine") // Specifying bean name
public class PetrolEngine implements Engine {
@Override
public String getType() {
return "Petrol Engine";
}
}
// Electric engine implementation
@Component
public class ElectricEngine implements Engine {
@Override
public String getType() {
return "Electric Engine";
}
}
// Car class with injected dependencies
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(@Qualifier("petrolEngine") Engine engine) {
this.engine = engine;
}
public void start() {
System.out.println("Car is running with " + engine.getType());
}
}
Autowiring is working magic for boosting productivity. In real life you'll deal with lots of beans and their dependencies, and manual wiring becomes a nightmare. Spring automates that boilerplate. Autowiring is used in almost all Spring apps — from small REST services to complex microservice architectures.
Thanks to autowiring you can:
- Quickly switch between different implementations of an interface.
- Make testing easier by replacing dependencies with mocks.
- Focus on business logic instead of manually wiring beans.
Alright — now that we've got the idea of autowiring, let's move on: dive into the world of flexible and scalable development.
GO TO FULL VERSION