How to Create a Bean in Spring?
4. @Primary -> if multiple beans of the same type 5. Functional registration -> GraalVM Native 6. @Configuration is proxied -> method calls = cache
Junior Level
Ways to create a bean:
1. Annotations (most common):
@Service
public class UserService { }
@Component
public class EmailSender { }
2. @Bean in configuration:
@Configuration
public class Config {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Rule:
- Your own code ->
@Service,@Component - Third-party classes ->
@Bean
Middle Level
Stereotype Annotations
@Component // General component
@Service // Business logic
@Repository // Database access (automatic exception translation)
@Controller // Web controller
@RestController // REST API (@Controller + @ResponseBody)
How it works:
@ComponentScan -> scans packages
-> Found @Component
-> Created BeanDefinition
-> Registered in context
Java Config (@Bean)
@Configuration
public class InfrastructureConfig {
@Bean
@Primary // Primary bean if there are multiple
public ObjectMapper mapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
return mapper;
}
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
return new HikariDataSource();
}
}
FactoryBean
// For complex creation logic
public class MyFactoryBean implements FactoryBean<MyService> {
public MyService getObject() { return new MyService(); }
public Class<?> getObjectType() { return MyService.class; }
}
// Spring will call getObject() and register the result
Senior Level
CGLIB Proxying of @Configuration
@Configuration
public class Config {
@Bean
public A a() { return new A(); }
@Bean
public B b() {
return new B(a()); // Calling method a()!
}
}
// Spring proxies Config via CGLIB:
// -> First call to a() -> creates bean, caches it
// -> Second call to a() -> returns from cache!
// -> Always one instance (Singleton)
Programmatic Registration (Spring 5+)
// Without reflection and annotation scanning -> faster startup.
// Spring Boot uses this for GraalVM Native Image, where reflection is limited.
context.registerBean(MyService.class, () -> new MyService());
// For GraalVM Native Image -> reflection is limited
// Programmatic registration is preferred
ImportSelector / ImportBeanDefinitionRegistrar
// ImportSelector - an interface that allows dynamically importing bean classes by condition. This is how all @Enable... annotations work (e.g., @EnableCaching, @EnableScheduling).
// ASM - library for reading class bytecode without loading them into the JVM. Spring uses it during @ComponentScan to find annotations.
// This is how all @Enable... annotations work
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
return new String[] { "com.example.Service1", "com.example.Service2" };
}
}
// How it is used:
@Import(MyImportSelector.class)
public @interface EnableMyFeature { }
// Now @EnableMyFeature imports Service1 and Service2
@EnableMyFeature // -> Imports Service1 and Service2
Scanning: ASM Library
ClassPathBeanDefinitionScanner:
-> Reads bytecode via ASM
-> Does NOT load classes into JVM!
-> Searches for annotations
-> Creates BeanDefinition
-> On large projects -> slow startup
-> Limit @ComponentScan to specific packages
When NOT to Use @Bean
- Simple data objects - DTOs, Entities - created via
newor ORM - Too many beans - each bean = startup overhead. For 1000+ beans consider lazy initialization
- FactoryBean - needed only when creation logic is complex and depends on external conditions (e.g., EntityManagerFactory)
Production Experience
Real scenario: slow startup
- 10,000 classes in classpath
- Scanning: 15 seconds
- Solution: limited @ComponentScan(“com.myapp”)
- Result: startup 2 seconds
Best Practices
- @Service/@Component -> your own code
- @Bean -> third-party libraries
- Limit scanning -> specific packages
- @Primary -> if multiple beans of the same type
- Functional registration -> GraalVM Native
- @Configuration is proxied -> method calls = cache
Summary for Senior
- @Component -> scanning via ASM
- @Bean -> Java Config, CGLIB proxy
- FactoryBean -> complex creation logic
- registerBean -> programmatically, GraalVM
- ImportSelector -> @Enable… annotations
- Limit scanning -> faster startup
Interview Cheat Sheet
Must know:
- Your own code ->
@Service,@Component,@Repository,@Controller(automatic scanning) - Third-party libraries ->
@Beanin@Configuration(manual configuration) @ComponentScanscans packages via ASM (reading bytecode without loading classes)@Configurationis proxied via CGLIB: repeated method calls return cached Singleton@Primary- primary bean if there are multiple of the same typeFactoryBean- for complex creation logic (Spring callsgetObject())- Programmatic registration
registerBean()- GraalVM Native Image, no reflection ImportSelector- dynamic bean import, this is how all@Enable...annotations work
Common follow-up questions:
- Why does calling
a()inside@Configurationreturn the same bean? CGLIB proxy intercepts the call and returns the Singleton from cache. - Why limit
@ComponentScan? On large projects, scanning thousands of classes slows down startup (15 sec -> 2 sec). - When to use
FactoryBean? When creation depends on external conditions (e.g., EntityManagerFactory). - What is ImportSelector? An interface for dynamic import of bean classes - the basis of all
@Enable...annotations.
Red flags (DO NOT say):
- “You can declare
@Beanfor DTO/Entity” (these are regular data objects, not beans) - “The bigger
@ComponentScan, the better - Spring will find everything” (slow startup on large projects) - ”
@Configurationmethods are called directly” (CGLIB proxy intercepts and caches) - “FactoryBean and
@Beanare the same” (FactoryBean is an interface with complex creation logic,@Beanis a simple method)
Related topics:
- [[04. What is a Bean in Spring]]
- [[06. What is Bean Lifecycle]]
- [[07. What are the Bean Lifecycle stages]]
- [[01. What is Dependency Injection]]
- [[08. What is BeanPostProcessor]]