Backend Development 15 min read

Unlock the Full Power of Spring’s @Autowired: Advanced Techniques and Common Pitfalls

This article explores Spring’s @Autowired annotation in depth, covering default wiring, handling multiple beans, using @Qualifier and @Primary, various injection targets, advanced collection injection, common pitfalls, and differences from @Resource, providing practical code examples and solutions for real‑world projects.

macrozheng
macrozheng
macrozheng
Unlock the Full Power of Spring’s @Autowired: Advanced Techniques and Common Pitfalls

Introduction

While reviewing other developers' code I noticed several unconventional uses of

@Autowired

. Curious, I investigated and discovered many powerful features that go beyond the typical field injection.

1. Default Autowiring

In Spring,

@Autowired

performs automatic injection, usually by type (

byType

). The

required

attribute defaults to

true

, enabling injection; set it to

false

to disable.

<code>package com.sue.cache.service;

import org.springframework.stereotype.Service;

@Service
public class TestService1 {
    public void test1() {}
}
</code>
<code>package com.sue.cache.service;

import org.springframework.stereotype.Service;

@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;

    public void test2() {}
}
</code>

Because Spring defaults to

byType

, the above works.

2. Multiple Beans of the Same Type

If more than one bean of the same type exists,

byType

cannot decide which one to inject, leading to conflicts.

Creating a duplicate class

TestService1

in a different package causes a

ConflictingBeanDefinitionException

at startup.

Spring derives bean names from class names (lower‑casing the first letter). Duplicate names cause bean definition conflicts.

To deliberately create two beans of the same type, you can define them in a @Configuration class:

<code>public class TestConfig {
    @Bean("test1")
    public TestService1 test1() { return new TestService1(); }

    @Bean("test2")
    public TestService1 test2() { return new TestService1(); }
}
</code>

Starting the application still fails because the bean name

testService1

is duplicated.

Another scenario is having two implementations of an interface:

<code>public interface IUser { void say(); }
</code>
<code>@Service
public class User1 implements IUser { @Override public void say() {} }
</code>
<code>@Service
public class User2 implements IUser { @Override public void say() {} }
</code>
<code>@Service
public class UserService {
    @Autowired
    private IUser user;
}
</code>

Spring cannot decide which implementation to inject.

3. @Qualifier and @Primary

When

byType

is insufficient, you can switch to name‑based injection (

byName

) using

@Qualifier

:

<code>@Service
public class UserService {
    @Autowired
    @Qualifier("user1")
    private IUser user;
}
</code>

Alternatively, mark one implementation with

@Primary

so that it is chosen automatically:

<code>@Primary
@Service
public class User1 implements IUser { @Override public void say() {} }
</code>

Removing

@Qualifier

from the consumer then works because Spring picks the primary bean.

When multiple candidates exist, the bean annotated with @Primary is selected.

4. Injection Targets

@Autowired

can be placed on five different elements.

4.1 Field

<code>@Service
public class UserService {
    @Autowired
    private IUser user;
}
</code>

4.2 Constructor

<code>@Service
public class UserService {
    private IUser user;
    @Autowired
    public UserService(IUser user) {
        this.user = user;
        System.out.println("user:" + user);
    }
}
</code>
Annotating the constructor still uses Spring’s autowiring mechanism; it does not become a pure constructor injection.

4.3 Method

<code>@Service
public class UserService {
    @Autowired
    public void test(IUser user) { user.say(); }
}
</code>

Spring invokes such methods once during startup, which can be used for initialization.

<code>@Service
public class UserService {
    private IUser user;
    @Autowired
    public void setUser(IUser user) { this.user = user; }
}
</code>

4.4 Parameter

<code>@Service
public class UserService {
    @Autowired
    public UserService(@Autowired IUser user) { this.user = user; }
}
</code>
<code>@Service
public class UserService {
    public void test(@Autowired IUser user) { user.say(); }
}
</code>

4.5 Annotation (rarely used)

Other annotations can also be meta‑annotated with

@Autowired

, but this is uncommon.

5. Advanced Collection Injection

@Autowired

can inject all beans of a given type into collections:

<code>@Service
public class UserService {
    @Autowired
    private List&lt;IUser&gt; userList;
    @Autowired
    private Set&lt;IUser&gt; userSet;
    @Autowired
    private Map&lt;String, IUser&gt; userMap;

    public void test() {
        System.out.println("userList:" + userList);
        System.out.println("userSet:" + userSet);
        System.out.println("userMap:" + userMap);
    }
}
</code>

Calling the corresponding endpoint prints two elements in each collection, confirming that Spring aggregates all matching beans.

6. When @Autowired Fails

6.1 Missing Stereotype Annotation

If a class lacks @Component, @Service, @Controller, @Repository, etc., Spring cannot create the bean, and injection will be null.

6.2 Autowiring in Filters or Listeners

Filters and listeners are initialized before Spring’s DispatcherServlet, so beans are not yet available. Attempting to @Autowired inside them causes startup failures.

Solution: obtain the ApplicationContext manually:

<code>public class UserFilter implements Filter {
    private IUser user;
    @Override
    public void init(FilterConfig filterConfig) {
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
        this.user = (IUser) ctx.getBean("user1");
        user.say();
    }
    // other methods omitted
}
</code>

6.3 Component Scan Missed

If @ComponentScan (or the implicit scan from @SpringBootApplication) does not cover a package, annotated classes are ignored and cannot be autowired.

6.4 Circular Dependencies

Spring can resolve most circular dependencies for singleton beans, but prototype beans or beans created via proxies may still cause failures.

7. Difference Between @Autowired and @Resource

@Autowired defaults to by‑type injection; @Resource defaults to by‑name.

@Autowired has a single

required

attribute; @Resource offers

name

and

type

among others.

To achieve by‑name with @Autowired you need @Qualifier; @Resource uses

name

directly.

@Autowired can be used on constructors, methods, parameters, fields, and annotations; @Resource works on classes, fields, and methods.

@Autowired is Spring‑specific; @Resource is a JSR‑250 standard.

Injection order also differs: @Autowired follows a by‑type resolution flow, while @Resource follows a name‑first, then type‑first strategy.

Conclusion

Understanding the nuances of

@Autowired

—its default behavior, how to handle multiple candidates, the role of @Qualifier and @Primary, various injection points, collection injection, common failure scenarios, and its distinction from @Resource—enables more robust and maintainable Spring applications.

backendJavaSpringDependency InjectionAutowired
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.