Skip to main content

Command Palette

Search for a command to run...

Techniques to Master Bean Visibility in Spring: Comparing @ComponentScan, @Import, and @Configuration

In any well-structured Spring application, managing bean visibility is like managing the backstage of a theater—too much exposure and you lose control; too little and your actors (beans) can’t perform. Developers often rely on annotations like @C...

Published
6 min read
Techniques to Master Bean Visibility in Spring: Comparing @ComponentScan, @Import, and @Configuration
T

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.

1. Understanding the Fundamentals

1.1 The Role of Bean Visibility

Bean visibility defines which beans are available for dependency injection in a given ApplicationContext. In small applications, everything lives in one context. But in larger, modularized applications, bean scoping becomes crucial—especially when splitting components into modules or when embedding one context within another (e.g., in plugin systems, or internal SDKs).

For example, in a multi-module setup like:

core-module/
├─ services/
├─ configs/
web-module/
├─ controllers/
├─ dto/

You don’t want web-module to automatically see all beans from core-module. You want fine-grained control over exposure—and that’s where these annotations come in.

2. @ComponentScan – Discovering Beans Automatically

2.1 How @ComponentScan Works

@ComponentScan is like Spring’s radar—it scans the package and its subpackages for components annotated with @Component, @Service, @Repository, or @Controller.

@Configuration
@ComponentScan(basePackages = "com.example.core.services")
public class CoreConfig {
}

This tells Spring:

“Hey, scan everything under com.example.core.services and register them as beans.”

By default, it scans the same package where the class is declared and below. That’s why package structure matters a lot in Spring Boot projects.

2.2 Limitations of @ComponentScan

While convenient, @ComponentScan can become too broad:

  • It may unintentionally expose beans to other modules.
  • Harder to trace which beans are loaded.
  • Risk of “bean duplication” if multiple contexts overlap scans.

Example problem:

@Configuration
@ComponentScan("com.example")
public class AppConfig {}

If both core and web are under com.example, you might accidentally bring backend service beans into your web module context—even ones you didn’t want.

That’s why experienced developers use narrower base packages or explicit imports instead of a sweeping scan.

3. @Import – The Explicit Connector

3.1 How @Import Works

@Import gives you surgical precision—it lets you directly import specific configuration classes or even plain component classes into the context.

Example:

@Configuration
@Import(CoreConfig.class)
public class WebConfig {
}

Here, Spring doesn’t perform any scanning; it just registers whatever beans CoreConfig defines (and only those).

You can even import multiple configs:

@Configuration
@Import({CoreConfig.class, SecurityConfig.class})
public class AppConfig {
}

This approach offers explicit visibility and avoids classpath scanning. It’s perfect for modular architectures where you want one module to opt-in to another’s beans rather than inheriting them implicitly.

3.2 Importing Non-Configuration Classes

A lesser-known trick: @Import can also register plain classes (not annotated with @Configuration) as beans.

public class UtilityBean {
public String getInfo() {
return "Hello from UtilityBean";
}
}

@Configuration
@Import(UtilityBean.class)
public class UtilConfig {}

Spring will automatically instantiate UtilityBean and make it available in the context.
No @Component required. This is incredibly useful for internal shared beans you don’t want scanned globally.

4. @Configuration – The Blueprint of Bean Creation

4.1 How @Configuration Defines Beans

Think of @Configuration as the “factory manager.” It holds @Bean methods that explicitly define how beans are created.

@Configuration
public class ServiceConfig {
@Bean
public EmailService emailService() {
return new EmailService();
}
}

This ensures full control over bean instantiation—no magic, no scanning.
Every @Bean is a contract between your module and Spring’s container.

4.2 Full vs Lite @Configuration Modes

Spring actually differentiates between full and lite configuration modes.

  • Full mode (@Configuration): uses CGLIB proxies to ensure singleton behavior.
  • Lite mode (class annotated with @Component containing @Bean methods): does not proxy, so beans may be re-instantiated if called directly.

Example to illustrate:

@Component
public class LiteConfig {
@Bean
public Foo foo() {
return new Foo();
}
}

Calling foo() manually here creates a new instance each time, unlike in a true @Configuration class.
Therefore, always prefer @Configuration for reliable singletons and predictable dependency graphs.

5. Combining the Three Annotations

5.1 Real-World Example

Let’s say we have a modular system with two layers: Core Services and Web Application.

Core Module:

@Configuration
@ComponentScan("com.example.core.services")
public class CoreConfig {
@Bean
public AuditService auditService() {
return new AuditService();
}
}

Web Module:

@Configuration
@Import(CoreConfig.class)
@ComponentScan("com.example.web.controllers")
public class WebConfig {
}

Here’s what happens:

  • @ComponentScan in WebConfig loads only controllers.
  • @Import(CoreConfig.class) explicitly pulls in service beans from the core module.
  • No accidental scanning beyond intended boundaries.

This pattern provides clean separation and predictable behavior—ideal for enterprise-scale systems.

5.2 When to Use Which

| Annotation         | Best Use Case                                   | Pros                    | Cons                 |
| ------------------ | ----------------------------------------------- | ----------------------- | -------------------- |
| @ComponentScan | For auto-detection in monolithic or simple apps | Convenient | Risk of bean overlap |
| @Import | For modular, explicit linking between contexts | Precise control | Slightly verbose |
| @Configuration | For defining bean creation manually | Deterministic, powerful | Requires boilerplate |

6. Advanced Techniques and Insights

6.1 Combining with @Profile or @Conditional

You can combine these annotations with @Profile or @Conditional to control visibility based on environment.

@Configuration
@Profile("dev")
public class DevOnlyConfig {
@Bean
public MockEmailService mockEmailService() {
return new MockEmailService();
}
}

This ensures your mock beans are invisible in production, maintaining clean visibility boundaries.

6.2 Avoiding Bean Conflicts Across Modules

If multiple modules define beans with the same name or type, use:

  • @Qualifier for disambiguation
  • @Primary to set a default
  • or restructure your module imports to avoid conflicts entirely.

7. Conclusion: Strategic Bean Management

In large Spring projects, clarity in bean visibility can mean the difference between clean modularity and dependency chaos.

To recap:

  • Use @ComponentScan for convenience in small projects.
  • Use @Import when you want explicit, modular inclusion.
  • Use @Configuration to handcraft deterministic beans.

Understanding when to combine or separate these tools is part of mastering Spring’s modular architecture—where each annotation becomes a strategic choice rather than a habit.

Bean visibility isn’t just about avoiding conflicts—it’s about designing intentional boundaries between layers of your system. Treat it like software architecture, not configuration syntax.

If you have questions about combining @Import with @Profile or handling circular dependencies in modular beans, drop your question below — let’s discuss and refine this further together.

Read more at : Techniques to Master Bean Visibility in Spring: Comparing @ComponentScan, @Import, and @Configuration

More from this blog

T

tuanh.net

540 posts

Are you ready to elevate your Java, OOP, Spring, and DevOps skills? Look no further!