Skip to main content

2.3 Autowiring

← Back to Chapter 2: Spring Beans & Dependency Injection

Autowiring is Spring's automated dependency resolution mechanism. While Dependency Injection (DI) is the architectural pattern of providing dependencies from outside, autowiring is the runtime engine through which the Spring IoC container matches and binds collaborating beans together without manual configuration.


What is Autowiring?​

Autowiring means Spring automatically finds the required dependency and provides it to your class.

In simple words:

You don't create the object using new; Spring finds the required object and injects it for you.

In traditional Java applications, you explicitly create dependencies:

// Manual instantiation (tight coupling)
OrderRepository repository = new OrderRepository();
OrderService service = new OrderService(repository);

With Autowiring, you instruct Spring to inspect the dependencies required by your component and automatically locate and inject the appropriate bean from the ApplicationContext:

@Service
public class OrderService {

private final OrderRepository orderRepository;

// Spring detects OrderRepository and automatically provides the bean
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}

What does @Autowired do?​

@Autowired tells Spring:

Find a suitable bean of this type and inject it here.

Dependency Injection vs Autowiring​

ConceptWhat It IsRole
Dependency Injection (DI)Architectural Design PatternThe philosophy: "A class should receive its dependencies rather than creating them."
AutowiringSpring Framework FeatureThe automated implementation: Spring discovers, matches, and binds those dependencies.

The Autowiring Process​

┌─────────────────────────────────────────────────────────────┐
│ Target Bean │
│ @Service │
│ public class OrderService { │
│ OrderRepository repository; │
│ } │
└──────────────────────────────┬──────────────────────────────┘
│
│ 1. Requires bean of type: OrderRepository
▼
┌─────────────────────────────────────────────────────────────┐
│ Spring IoC Container │
│ │
│ Bean Registry: │
│ ├── [OrderRepository (Singleton Bean)] │
│ ├── [PaymentGateway (Singleton Bean)] │
│ └── [EmailService (Singleton Bean)] │
│ │
│ Match found by type: OrderRepository │
└──────────────────────────────┬──────────────────────────────┘
│
│ 2. Automatically injects bean reference
▼
┌─────────────────────────────────────────────────────────────┐
│ OrderRepository is wired into OrderService at startup │
└─────────────────────────────────────────────────────────────┘

Types of Autowiring​

Spring supports @Autowired across three primary target locations:

When placed on a constructor, Spring supplies the bean arguments when instantiating the class.

@Service
public class PaymentService {

private final PaymentGateway paymentGateway;
private final NotificationService notificationService;

@Autowired // Explicit annotation (optional if single constructor)
public PaymentService(PaymentGateway paymentGateway, NotificationService notificationService) {
this.paymentGateway = paymentGateway;
this.notificationService = notificationService;
}
}

[!NOTE] Single Constructor Rule (Spring 4.3+): If a Spring bean defines only one constructor, the @Autowired annotation is completely optional. Spring will automatically use that constructor for dependency injection. If multiple constructors exist, you must explicitly mark one with @Autowired to instruct Spring which one to invoke.

@Service
public class ReportService {

private final Formatter formatter;
private final EmailService emailService;

// Default constructor for testing/fallback
public ReportService(Formatter formatter) {
this(formatter, null);
}

// Explicit @Autowired required because multiple constructors exist
@Autowired
public ReportService(Formatter formatter, EmailService emailService) {
this.formatter = formatter;
this.emailService = emailService;
}
}

2. Setter / Method Injection​

Useful when dependencies are optional or can be reconfigured dynamically at runtime.

@Service
public class NotificationManager {

private SMSGateway smsGateway;

@Autowired
public void setSmsGateway(SMSGateway smsGateway) {
this.smsGateway = smsGateway;
}
}

[!TIP] @Autowired can be placed on any configuration method, not just traditional Java bean setters matching setXxx() naming:

@Autowired
public void configureServices(SMSGateway sms, EmailGateway email) {
this.smsGateway = sms;
this.emailGateway = email;
}

3. Field Injection (Discouraged Anti-Pattern)​

Direct injection into private fields via Java reflection:

@Service
public class CustomerService {

@Autowired // Avoid in production code!
private CustomerRepository customerRepository;
}
  • Bypasses constructors, preventing immutability (final fields cannot be used).
  • Difficult to unit test without firing up a Spring test context or using reflection hacks.
  • Violates encapsulation and conceals dependency bloat.

How Spring Resolves Autowired Beans Under the Hood​

The heavy lifting of @Autowired is handled by the AutowiredAnnotationBeanPostProcessor, a core BeanPostProcessor registered automatically by Spring Boot:

@Autowired on injection point
│
▼
┌──────────────────────────────────────────────┐
│ Search BeanFactory for beans matching type │
└──────────────────────┬───────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
0 Beans 1 Bean > 1 Beans
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌───────────────┐ ┌─────────────────┐
│ Check if │ │ Exact match: │ │ Check for │
│ required=false │ │ Inject bean │ │ @Primary or │
│ Yes: inject null│ └───────────────┘ │ @Qualifier │
│ No: Throw │ │ │
│ NoSuchBean... │ │ Match: Inject │
└─────────────────┘ │ None: Throw │
│ NoUniqueBean... │
└─────────────────┘
  1. Type Matching (byType): Spring scans the container for candidate beans matching the target class or interface.
  2. Single Match: If exactly one matching bean exists, Spring injects it immediately.
  3. Zero Matches:
    • If required (default), Spring throws NoSuchBeanDefinitionException.
    • If marked optional, Spring assigns null or an empty container.
  4. Multiple Matches:
    • If one bean is marked with @Primary, it takes priority.
    • If an @Qualifier is present, Spring matches the qualifier name.
    • Spring falls back to matching the parameter or field name against candidate bean names (byName).
    • If ambiguity persists, Spring throws NoUniqueBeanDefinitionException.

@Primary​

We can mark one implementation as the default.

@Service
@Primary
public class CreditCardPaymentService
implements PaymentService {
}

Now when Spring sees:

PaymentService paymentService

it chooses the @Primary implementation.

PaymentService
|
------------
| |
@Primary UPI
CreditCard
↓
selected

@Qualifier​

@Qualifier lets us explicitly specify which Bean we want.

@Service("upiPayment")
public class UpiPaymentService
implements PaymentService {
}

Then:

@Service
public class OrderService {

private final PaymentService paymentService;

public OrderService(
@Qualifier("upiPayment")
PaymentService paymentService) {

this.paymentService = paymentService;
}
}

@Primary vs @Qualifier​

@Primary@Qualifier
Defines default BeanExplicitly selects Bean
Useful when one implementation is preferredUseful when specific implementation is required
Less explicitMore explicit

4. Handling Optional Dependencies​

By default, @Autowired is mandatory: if Spring cannot find a compatible candidate bean, application startup fails. You can handle non-mandatory dependencies using three approaches:

Approach 1: @Autowired(required = false)​

@Service
public class AuditService {

private AuditStorage auditStorage;

@Autowired(required = false)
public void setAuditStorage(AuditStorage auditStorage) {
this.auditStorage = auditStorage;
}

public void log(String event) {
if (auditStorage != null) {
auditStorage.save(event);
}
}
}

Constructor injection paired with Java 8's Optional clearly signals optionality without null checks:

@Service
public class PaymentProcessor {

private final Optional<FraudDetector> fraudDetector;

public PaymentProcessor(Optional<FraudDetector> fraudDetector) {
this.fraudDetector = fraudDetector;
}

public void processPayment(Payment payment) {
fraudDetector.ifPresent(detector -> detector.validate(payment));
// Proceed with transaction...
}
}

Approach 3: @Nullable​

import org.springframework.lang.Nullable;

@Service
public class EmailService {

private final TemplateEngine templateEngine;

public EmailService(@Nullable TemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
}

5. Legacy XML Autowiring Modes​

In legacy XML-based Spring configurations (beans.xml), autowiring modes were configured via the autowire attribute:

<bean id="orderService" class="com.example.OrderService" autowire="byType" />
Autowire ModeResolution StrategyDescription
no (default)NoneManual wiring only using explicit <property ref="..."> elements.
byNameBean NameInspects bean property names and matches beans having identical IDs.
byTypeClass / InterfaceInspects property types and matches beans of the same type.
constructorConstructor TypesInspects constructor parameter types and matches beans of those types.

In modern Spring Boot, annotation-based wiring (@Autowired) supersedes XML modes, defaulting to type-based inspection with name-based fallback.


6. @Autowired vs @Resource vs @Inject​

Spring supports standard Java EE / Jakarta EE dependency injection annotations alongside its native @Autowired:

┌─────────────────┬────────────────────────────────────────────────────────┐
│ Annotation │ Standard & Primary Matching Strategy │
├─────────────────┼────────────────────────────────────────────────────────┤
│ @Autowired │ Spring-native. Matches byType first, then byName. │
│ @Resource │ Jakarta EE (JSR-250). Matches byName first, byType. │
│ @Inject │ Jakarta EE (JSR-330 / CDI). Matches byType first. │
└─────────────────┴────────────────────────────────────────────────────────┘

Detailed Comparison Matrix​

Feature@Autowired@Resource@Inject
OriginSpring Framework (org.springframework.beans)Common Annotations (JSR-250 / Jakarta EE)Dependency Injection for Java (JSR-330)
Default MatchingbyType (with name fallback)byName (falls back to byType)byType (with qualifier fallback)
Target LocationsConstructors, Methods, Fields, ParametersFields, Setter methods (not constructors)Constructors, Methods, Fields
Optional Supportrequired = false, Optional<T>, @NullableNo built-in required attributeRequires Provider<T> or Optional<T>
Disambiguation@Qualifier("name")Matches via name attribute: @Resource(name = "x")@Named("name") or custom qualifiers
DependencyIncluded in Spring (spring-beans)jakarta.annotation:jakarta.annotation-apijakarta.inject:jakarta.inject-api

Example Using @Resource:​

@Service
public class OrderService {

// Matches bean with id="sqlOrderRepository", not MongoOrderRepository
@Resource(name = "sqlOrderRepository")
private OrderRepository orderRepository;
}

7. Real-World Gotchas & Interview Traps​

1. The new Operator Trap​

If you instantiate a class with new, Spring's container has no knowledge of the object. Any @Autowired dependencies inside it will remain null:

// ❌ WRONG: NullPointerException at runtime!
UserService service = new UserService();
service.doSomething(); // orderService inside UserService is null!

// ✅ CORRECT: Let Spring inject the managed bean
@Autowired
private UserService userService;

2. Autowiring static Fields Fails​

Spring cannot inject dependencies directly into static fields because static members belong to the class rather than a specific bean instance:

// ❌ WRONG: jwtTokenService remains null
@Service
public class SecurityUtils {
@Autowired
private static JwtTokenService jwtTokenService;
}

Solution: Use a non-static setter to initialize the static field:

// ✅ CORRECT: Setter-based static initialization
@Component
public class SecurityUtils {

private static JwtTokenService jwtTokenService;

@Autowired
public void setJwtTokenService(JwtTokenService service) {
SecurityUtils.jwtTokenService = service;
}

public static boolean validate(String token) {
return jwtTokenService.isValid(token);
}
}

3. Missing Default Constructor with Multiple Constructors​

If you declare multiple constructors without specifying @Autowired, Spring cannot guess which constructor to call during startup and throws NoSuchMethodException. Always mark the intended injection constructor with @Autowired.


8. Summary Checklist​

✔ Use constructor injection without @Autowired for single-constructor classes.
✔ Use @Autowired explicitly when multiple constructors are defined.
✔ Prefer Optional<T> or @Nullable over @Autowired(required = false) for optional dependencies.
✔ Avoid field injection in production code; it hinders unit tests and immutability.
✔ Remember that @Autowired matches byType first, then byName as a fallback.
✔ Never instantiate Spring beans manually using new if they contain autowired fields.

Next Up: Learn how to manage bean creation, memory allocation, and destroy phases in 2.4 Bean Scopes & Lifecycle.