How to create a Bean in Spring
In daily Spring and Spring Boot development, beans are declared and registered using two fundamental approaches:
🟢 Junior Level
1. The Two Primary Ways to Create Beans
In daily Spring and Spring Boot development, beans are declared and registered using two fundamental approaches:
Approach 1: Stereotype Annotations (For Your Application Code)
Mark your class with a stereotype annotation. Spring automatically discovers the class during classpath scanning (@ComponentScan), registers its BeanDefinition, and instantiates it:
@Service // Automatically registered as a Spring bean
public class OrderService {
public void processOrder() {
// Business logic
}
}
Approach 2: Java-Based Configuration via @Bean (For Third-Party Libraries)
When integrating third-party libraries (e.g., Jackson ObjectMapper, Apache Kafka clients, or HTTP clients), you cannot modify their source code to add @Component. Instead, declare an explicit @Bean method inside a @Configuration class:
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().findAndRegisterModules();
}
}
[!IMPORTANT] The Golden Rule:
- Internal application domain classes $\to$ Use stereotype annotations (
@Service,@Component,@Repository,@Controller).- External 3rd-party library classes $\to$ Use
@Beanfactory methods inside@Configurationclasses.
🟡 Middle Level
1. The 4 Stereotype Annotations
Spring provides specialized stereotype annotations that extend @Component with semantic roles:
| Stereotype Annotation | Architectural Layer | Special Container Features |
|---|---|---|
@Component |
Generic foundational building block | Basic container discovery and lifecycle management. |
@Service |
Business Logic / Domain Service layer | Semantic marker for transaction boundaries and domain logic. |
@Repository |
Data Access Layer (DAO) | Automatically triggers Persistence Exception Translation (converting native SQL/Hibernate exceptions into Spring’s unified DataAccessException hierarchy via PersistenceExceptionTranslationPostProcessor). |
@Controller / @RestController |
Presentation / Web Layer | Handles HTTP routing, request binding, validation, and JSON serialization. |
2. Specialized Complex Instantiation: FactoryBean<T>
When a bean requires complex programmatic initialization logic, dynamic SSL/TLS handshake setup, or integration with legacy factories, implement Spring’s FactoryBean<T> interface:
public class SecurityTokenClientFactoryBean implements FactoryBean<SecurityTokenClient> {
@Override
public SecurityTokenClient getObject() throws Exception {
// Multi-step complex initialization logic
return SecurityTokenClient.builder()
.withEndpoint("https://auth.internal")
.withCertificate(loadKeyStore())
.build();
}
@Override
public Class<?> getObjectType() {
return SecurityTokenClient.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
The Ampersand (&) Dereference Operator:
context.getBean("tokenClient")returns the product of the factory (SecurityTokenClient).context.getBean("&tokenClient")(prefixed with&) returns theFactoryBeaninstance itself (SecurityTokenClientFactoryBean).
3. Classpath Scanning Under the Hood: ASM Bytecode Inspection
How does Spring scan thousands of classes in large enterprise codebases without crashing JVM Metaspace?
Spring does not call ClassLoader.loadClass() or Class.forName() on every file in your classpath!
- Spring’s
ClassPathBeanDefinitionScannerreads compiled.classfiles directly from disk as raw binary byte streams using the ASM bytecode manipulation library (SimpleMetadataReaderFactory). - ASM parses the class header and constant pool to read annotations (e.g., checking if
@Componentis present) without loading the class into the JVM Metaspace. - It creates a lightweight
ScannedGenericBeanDefinitionmetadata blueprint. - Classes are only loaded by the
ClassLoaderwhen the bean is actually instantiated during container startup, avoiding static initializer side-effects and memory leaks.
🔴 Senior Level
1. @Configuration Architecture: Full Mode vs Lite Mode
A classic senior interview question explores what happens when one @Bean method directly invokes another:
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate() {
// Direct method call! Does this instantiate a second HikariDataSource?
return new JdbcTemplate(dataSource());
}
}
Full Configuration Mode (Default: proxyBeanMethods = true):
In @Configuration classes, Spring creates a CGLIB dynamic subclass proxy (DatabaseConfig$$SpringCGLIB$$0):
- When
jdbcTemplate()invokesdataSource(), the CGLIBMethodInterceptorintercepts the call. - It checks the
BeanFactorysingleton cache. If thedataSourcebean already exists, it returns the cached singleton instance, completely bypassing execution of the method body! - Guarantees the singleton contract is preserved.
Lite Configuration Mode (proxyBeanMethods = false or @Bean in @Component):
If @Configuration(proxyBeanMethods = false) is used, or if @Bean is placed inside a standard @Component class:
- No CGLIB proxy is generated.
- Calling
dataSource()directly insidejdbcTemplate()behaves like plain Java: it executes the method body and instantiates a second, unmanagedHikariDataSourceobject! This causes connection pool leaks and broken transaction synchronization.
[!TIP] Idiomatic Pattern in Lite Mode: Never invoke
@Beanmethods directly. Instead, declare dependencies as method parameters:@Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { // Spring injects the singleton! return new JdbcTemplate(dataSource); }Spring Boot 2.2+ internal auto-configurations use
proxyBeanMethods = falseto accelerate startup and reduce Metaspace memory footprint.
2. Dynamic Bean Registration: ImportSelector & @Enable* Annotations
All Spring feature toggle annotations (@EnableAsync, @EnableTransactionManagement, @EnableScheduling) are powered by ImportSelector or ImportBeanDefinitionRegistrar:
public class FeatureToggleImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean useCloud = Boolean.getBoolean("app.cloud.enabled");
return useCloud
? new String[] { "com.example.cloud.AwsStorageService" }
: new String[] { "com.example.local.LocalStorageService" };
}
}
// Meta-annotation declaration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(FeatureToggleImportSelector.class)
public @interface EnableStorageModule { }
This enables Spring to conditionally and dynamically register complete clusters of beans at runtime without hardcoding them in component scan paths.
3. Functional Bean Registration (Spring 5+ / GraalVM Native AOT)
In modern cloud-native architectures compiling to GraalVM Native Images, traditional reflection and classpath scanning represent major bottlenecks. Spring 5 introduced programmatic functional registration:
GenericApplicationContext context = new GenericApplicationContext();
// Reflection-free, supplier-based bean registration:
context.registerBean(UserService.class, () -> new UserService(context.getBean(UserRepository.class)));
- Zero Reflection: Directly calls Java constructors via lambdas.
- Instant Cold Starts: Minimizes JVM warmup and Metaspace memory footprint, enabling sub-millisecond container startups in serverless environments.
4 Tricky Questions
1. Why does invoking a @Bean method from another @Bean method in a @Configuration class return the same singleton, but in a @Component class it creates a duplicate?
Answer:
A @Configuration class runs in Full Mode (proxyBeanMethods = true by default). Spring enhances the class at startup using CGLIB bytecode generation. When a @Bean method is invoked internally, the CGLIB MethodInterceptor intercepts the call, queries Spring’s DefaultListableBeanFactory singleton cache, and returns the existing bean reference without executing the method body again.
In contrast, a @Component class containing @Bean methods runs in Lite Mode. Spring does not generate a CGLIB subclass. Calling a @Bean method directly executes plain Java bytecode, instantiating a completely new, unmanaged object that violates the singleton invariant.
2. How do you retrieve the actual FactoryBean implementation instance rather than the object it produces?
Answer:
To retrieve the FactoryBean itself from the ApplicationContext, prefix the bean name with the ampersand (&) symbol:
// Returns the product created by FactoryBean.getObject():
SecurityTokenClient client = context.getBean("tokenClient", SecurityTokenClient.class);
// Returns the actual FactoryBean factory instance:
SecurityTokenClientFactoryBean factory = context.getBean("&tokenClient", SecurityTokenClientFactoryBean.class);
Without the & prefix, Spring automatically calls FactoryBean.getObject() and unwraps the product.
3. How does Spring’s @ComponentScan read annotations from thousands of classes on the classpath without causing JVM Metaspace memory exhaustion?
Answer:
Spring avoids using Class.forName() or ClassLoader.loadClass(), which would force the JVM to load every .class file into the permanent Metaspace and trigger static initializers.
Instead, ClassPathBeanDefinitionScanner utilizes the ASM bytecode library (SimpleMetadataReaderFactory). ASM reads the compiled .class files directly from disk as raw binary input streams and parses the class headers and constant pool entries. It identifies stereotype annotations (@Component) purely by inspecting raw bytecode tokens, registering lightweight BeanDefinition metadata while leaving the actual classes unloaded until instantiation.
4. In Lite Mode (@Configuration(proxyBeanMethods = false)), how should one @Bean method depend on another bean without breaking the singleton contract?
Answer:
Instead of calling the other @Bean method directly (return new JdbcTemplate(dataSource());), you must declare the collaborator as a parameter in the method signature:
@Configuration(proxyBeanMethods = false)
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) { // Injected by Spring IoC!
return new JdbcTemplate(dataSource);
}
}
When Spring executes the jdbcTemplate factory method, it looks up the managed DataSource singleton from the container and injects it into the parameter, preserving the singleton contract without needing CGLIB proxying.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“In Spring, beans can be created through five core mechanisms:
- Stereotype Annotations (
@Component,@Service,@Repository,@Controller) for application code, detected via ASM bytecode scanning in@ComponentScan.- Java Configuration (
@Beanin@Configuration) for third-party classes, where CGLIB proxies (Full Mode) preserve singleton semantics across inter-bean method calls.FactoryBean<T>for complex programmatic construction (accessible via&beanName).- Dynamic Import (
ImportSelector/ImportBeanDefinitionRegistrar) powering all@Enable*modules.- Functional Registration (
registerBean()) introduced in Spring 5 for reflection-free GraalVM Native Image compilation.”
Red Flags & Pitfalls (DO NOT Say)
- ❌ “Calling
@Beanmethods inside@Componentpreserves the singleton pattern just like in@Configuration.” (@Componentruns in Lite Mode without CGLIB proxying, creating duplicate instances). - ❌ “Spring loads every single class in the classpath into JVM memory during
@ComponentScan.” (Spring uses ASM to inspect.classbytecode directly without classloading). - ❌ “FactoryBean and
@Beanmethods are identical.” (FactoryBeanis an SPI interface supporting complex, multi-step programmatic lifecycles). - ❌ “You should create beans for mutable DTOs and JPA Entities.” (Beans are intended for stateless architectural components, not domain data structures).
Bean Creation Strategy Decision Flow
Bean Creation Decision Matrix:
├─ Is it your own internal application code?
│ └─ YES ──> Use Stereotype Annotations (@Service, @Repository, @Component)
└─ Is it a 3rd-party library class, or requires complex configuration?
├─ Standard instantiation? ──> @Bean inside @Configuration
│ ├─ Inter-bean calls? ──> proxyBeanMethods = true (Full Mode)
│ └─ Max performance? ──> proxyBeanMethods = false + method params
├─ Complex dynamic lifecycle? ─> Implement FactoryBean<T>
└─ Cloud-native / GraalVM AOT? ─> Functional context.registerBean()