Spring Bean : Scopes, Lifecycle, and Best Practices
What is a Java Bean?
A Java Bean is simply a Java class that follows specific conventions:
| Convention | Requirement |
|---|---|
| No-arg constructor | Must have a public default constructor |
| Private fields | Properties are private |
| Getters/Setters | Public accessor methods (getX(), setX(), isX() for booleans) |
| Serializable | Often implements Serializable (optional in Spring) |
// A simple Java Bean
@Getter @Setter @NoArgsConstructor
public class Person {
private String name;
private int age;
public Person() {} // no-arg constructor
}
Spring Bean vs Java Bean
| Java Bean | Spring Bean |
|---|---|
| Follows getter/setter conventions | Any object managed by Spring IoC container |
Created by you with new
|
Created and managed by Spring |
| No lifecycle management | Has lifecycle (creation → initialization → use → destruction) |
| No dependency injection | Dependencies injected automatically |
Spring Bean = Any object that Spring creates, configures, and manages.
// This is a Spring Bean (managed by Spring)
@Component
public class OrderService {
private final PaymentService paymentService; // injected by Spring
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
@Bean Annotation
The @Bean annotation is used on methods/constructor (not classes) to explicitly declare a bean and register it with the Spring container.
Why Use @Bean on a Method?
While @Component (and its stereotypes like @Service, @Repository) are used on classes to let Spring auto-detect and register beans via component scanning,
@Bean is used when:
-
You don’t own the class - The class comes from a third-party library, so you can’t add
@Componentto it. - You need custom instantiation logic - The bean requires specific constructor arguments or configuration.
- You want explicit control - You prefer programmatic bean definition over auto-detection.
How It Works
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}
- The method name (
restTemplate,objectMapper) becomes the bean name by default. - The return type is the bean type that Spring will manage.
- Spring calls this method and registers the returned object as a bean in the ApplicationContext.
@Bean vs @Component
| Aspect | @Bean |
@Component |
|---|---|---|
| Applied to | Methods (inside @Configuration class) |
Classes |
| Use case | Third-party classes, custom instantiation | Your own classes |
| Discovery | Explicit declaration | Auto-detected via component scan |
| Flexibility | Full control over instantiation | Limited to default/autowired constructors |
Example: Third-Party Library Bean
You cannot modify RestTemplate source code to add @Component, so you use @Bean:
@Configuration
public class HttpClientConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
}
Bean Naming
@Bean // Bean name: "myService" (method name)
public MyService myService() { ... }
@Bean("customName") // Bean name: "customName"
public MyService myService() { ... }
@Bean(name = {"name1", "alias1"}) // Multiple names/aliases
public MyService myService() { ... }
Dependency Injection in @Bean Methods
Spring automatically injects dependencies as method parameters:
@Bean
public UserService userService(UserRepository userRepository, EmailService emailService) {
return new UserService(userRepository, emailService);
}
Spring resolves UserRepository and EmailService from the container and passes them to the method.
Why Register Beans in Spring?
Registration tells Spring: “This object exists, manage it for me.”
Without Registration (Manual Wiring)
// You must create and wire everything manually
public class Application {
public static void main(String[] args) {
// Create dependencies manually
DatabaseConnection db = new DatabaseConnection("jdbc:mysql://...");
UserRepository userRepo = new UserRepository(db);
EmailService emailService = new EmailService();
UserService userService = new UserService(userRepo, emailService);
OrderService orderService = new OrderService(userService);
// 😩 Painful for large applications with 100+ classes
}
}
With Registration (Spring Manages It)
@Component
public class DatabaseConnection { }
@Repository
public class UserRepository {
@Autowired private DatabaseConnection db;
}
@Service
public class UserService {
@Autowired private UserRepository userRepo;
@Autowired private EmailService emailService;
}
// Spring automatically:
// 1. Finds all @Component classes
// 2. Creates instances in correct order
// 3. Injects dependencies
// 4. Manages lifecycle
Benefits of Bean Registration
| Benefit | Description |
|---|---|
| Dependency Injection | Spring injects dependencies automatically |
| Lifecycle Management | Spring handles creation, initialization, destruction |
| Singleton by Default | One instance shared across application (saves memory) |
| Loose Coupling | Classes don’t create their own dependencies |
| Easy Testing | Swap real beans with mocks easily |
| Configuration | Change behavior via properties without code changes |
How to Register Beans
-
Stereotype Annotations (@Component, @Service, @Repository, @Controller)
- Use
@ComponentScanor@SpringBootApplicationto auto-discover.@Component public class EmailService { }
- Use
-
Java
@ConfigurationClasses with@Beanmethods (explicit registration):@Configuration public class AppConfig { @Bean public CacheService cacheService() { return new CacheService(); } } -
XML Configuration (legacy):
<bean id="emailService" class="com.example.EmailService" />
Bean Scopes: Why They Matter
Scope determines how many instances of a bean exist and how long they live.
Available Scopes
| Scope | Instances | Lifetime | Use Case |
|---|---|---|---|
singleton |
1 per container | Application lifetime | Stateless services (default) |
prototype |
New instance per request | Until garbage collected | Stateful objects |
request |
1 per HTTP request | Single HTTP request | Request-specific data |
session |
1 per HTTP session | User session | User session data |
application |
1 per ServletContext | Application lifetime | Shared across servlets |
websocket |
1 per WebSocket | WebSocket session | WebSocket-specific data |
Singleton Scope (Default) — One Instance
Singleton scope is the default scope in Spring, where only one instance of the bean is created and shared throughout the application context.
- The singleton scope is the default scope in Spring.
- The
Gang of Fourdefines Singleton as having one instance per ClassLoader.- in their book Design Patterns: Elements of Reusable Object-Oriented Software-, ensures that a class has only one instance and provides a global point of access to it.
- However, Spring singleton is defined as one instance of bean definition per
container.
- In the Spring Framework, a singleton bean is defined as a bean that is instantiated only once per Spring IoC container.
- This means that within a single container, there will be only one instance of a bean definition.
@Component
public class SingletonBean {
// Bean definition
}
Use Cases:
-
Stateless Services:
- Beans that don’t hold any client-specific or mutable data.
- Example: Service classes, DAOs.
-
Configuration Beans:
- Classes used for setting up application-wide configurations.
- Example:
@Configurationannotated beans.
-
Shared Utilities:
- Beans providing reusable functionality, like logging or caching utilities.
Advantages:
- Efficient memory usage.
- Shared state across the application.
@Service // singleton by default
public class PaymentService {
// Same instance injected everywhere
// ⚠️ Must be thread-safe (no mutable instance state)
}
┌─────────────────────────────────────────────────┐
│ Spring Container │
│ ┌─────────────────────────────────────────┐ │
│ │ PaymentService (single instance) │ │
│ └─────────────────────────────────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ ┌──────┴──┐ ┌──────┴──┐ ┌──────┴──┐ │
│ │ OrderA │ │ OrderB │ │ OrderC │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
All orders share the SAME PaymentService instance
Prototype Scope — New Instance Each Time
Prototype scope instructs the Spring IoC container to create a new instance of the bean whenever it is requested.
@Component
@Scope("prototype")
public class PrototypeBean {
// Bean definition
}
Use Cases:
-
Stateful Components:
- Beans holding temporary or client-specific data.
- Example: Objects with user session details.
-
Expensive Objects:
- Large objects that need different configurations for each use.
- Example: Heavy object initialization with variable parameters.
-
Multi-threaded Applications:
- Beans where thread-safety requires separate instances per thread.
Advantages:
- Isolation between instances.
- Ideal for beans with mutable state.
@Component
@Scope("prototype")
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
// Each user gets their own cart instance
}
┌─────────────────────────────────────────────────┐
│ Spring Container │
│ │
│ getBean(ShoppingCart.class) → new instance │
│ getBean(ShoppingCart.class) → new instance │
│ getBean(ShoppingCart.class) → new instance │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Cart 1 │ │ Cart 2 │ │ Cart 3 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
Each request gets a NEW ShoppingCart instance
Request Scope — Per HTTP Request
A new instance of the bean is created for each HTTP request in a web application. Request scope is used in web-based applications, where a new instance of the bean is created once per HTTP request.
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
// Bean definition
}
Use Cases:
-
Request-Specific Data Processing:
- Beans holding data that is specific to a single HTTP request.
- Example: Request validation or processing logic.
-
Controllers and Form Handlers:
- Beans to manage user input and request processing in MVC applications.
-
Temporary Attributes:
- Beans required to calculate data for a single view or response.
Advantages:
- Scoped to a single HTTP request lifecycle.
- Avoids memory leaks from request-specific data in global beans.
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String correlationId;
private long startTime;
// Fresh instance for each HTTP request
}
Session Scope — Per HTTP Session
A new instance of the bean is created for each HTTP session in a web application. Session scope is tied to the HTTP session lifecycle,
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedBean {
// Bean definition
}
Use Cases:
-
User-Specific Session Data:
- Beans holding user preferences, shopping cart data, or session-specific attributes.
- Collaborative Web Applications
-
Authentication and Authorization:
- Beans to store user credentials or session tokens during the session lifecycle.
-
Stateful Web Applications:
- Applications requiring user-specific state across multiple requests.
Advantages:
- Isolated state per user session.
- Useful for managing user-specific resources.
Why Use Session Beans in Multithreading?
State Isolation:
Each user’s session has its own instance of the bean. Even when multiple threads handle requests concurrently, the state remains tied to the specific session.
Simplified State Management:
The bean eliminates the need to manually pass session attributes or manage synchronization across requests.
Real-World Examples:
Shopping Carts: Multiple requests for adding/removing items in a single session. Collaborative Workflows: Multiple operations (e.g., saving drafts, updating settings) performed concurrently by a user.
Application Scope — Per Servlet Context
A single instance of the bean is created for the entire servlet context. created once when the application starts up.
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationScopedBean {
// Bean definition
}
Use Cases:
-
Application-Wide Resources:
- Beans managing shared resources, like global settings or configuration.
-
Servlet Context Attributes:
- Beans wrapping context-level attributes or data.
-
Cache or Resource Pools:
- Objects reused by all users, such as database connections or thread pools.
Advantages:
- Shared across the entire application lifecycle.
- Suitable for long-lived resources.
Why Scope Matters: The Singleton + Prototype Problem
@Service // singleton
public class OrderService {
@Autowired
private ShoppingCart cart; // prototype
// ⚠️ PROBLEM: cart is injected ONCE at startup
// All requests share the same cart!
}
// Solution: Use Provider or ObjectFactory
@Service
public class OrderService {
@Autowired
private Provider<ShoppingCart> cartProvider;
public void processOrder() {
ShoppingCart cart = cartProvider.get(); // new instance each time
}
}
Thread-safety and Scope
- Singleton beans must be thread-safe (no mutable shared state) or must guard mutable state properly (synchronization, thread-local, or use stateless design).
- Prototype/request/session beans can safely hold state for their intended lifetime (but be careful when exposing prototype-scoped instances to multiple threads).
Bean Lifecycle
Spring beans go through a well-defined lifecycle with hooks for custom logic.
Lifecycle Phases
┌─────────────────────────────────────────────────────────────────┐
│ BEAN LIFECYCLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. INSTANTIATION │
│ └── Spring calls constructor │
│ │ │
│ ▼ │
│ 2. POPULATE PROPERTIES │
│ └── @Autowired fields/setters injected │
│ │ │
│ ▼ │
│ 3. BEAN NAME AWARE │
│ └── setBeanName() if BeanNameAware │
│ │ │
│ ▼ │
│ 4. BEAN FACTORY AWARE │
│ └── setBeanFactory() if BeanFactoryAware │
│ │ │
│ ▼ │
│ 5. PRE-INITIALIZATION (BeanPostProcessor) │
│ └── postProcessBeforeInitialization() │
│ │ │
│ ▼ │
│ 6. INITIALIZATION │
│ ├── @PostConstruct method │
│ ├── InitializingBean.afterPropertiesSet() │
│ └── Custom init-method │
│ │ │
│ ▼ │
│ 7. POST-INITIALIZATION (BeanPostProcessor) │
│ └── postProcessAfterInitialization() │
│ │ │
│ ▼ │
│ 8. BEAN READY FOR USE ✓ │
│ │ │
│ ▼ │
│ 9. DESTRUCTION (on container shutdown) │
│ ├── @PreDestroy method │
│ ├── DisposableBean.destroy() │
│ └── Custom destroy-method │
│ │
└─────────────────────────────────────────────────────────────────┘
Lifecycle Callbacks Example
@Component
public class DatabaseConnectionPool implements InitializingBean, DisposableBean {
private List<Connection> connections;
@Autowired
private DataSourceConfig config; // injected before @PostConstruct
// Called after dependency injection
@PostConstruct
public void init() {
System.out.println("1. @PostConstruct: Initializing pool");
connections = new ArrayList<>();
}
// InitializingBean interface method
@Override
public void afterPropertiesSet() {
System.out.println("2. afterPropertiesSet: Creating connections");
for (int i = 0; i < config.getPoolSize(); i++) {
connections.add(createConnection());
}
}
// Called before bean destruction
@PreDestroy
public void cleanup() {
System.out.println("3. @PreDestroy: Closing connections");
connections.forEach(Connection::close);
}
// DisposableBean interface method
@Override
public void destroy() {
System.out.println("4. destroy: Final cleanup");
}
}
Output on startup:
1. @PostConstruct: Initializing pool
2. afterPropertiesSet: Creating connections
Output on shutdown:
3. @PreDestroy: Closing connections
4. destroy: Final cleanup
Preferred Approach: Use Annotations
@Service
public class CacheService {
private Cache cache;
@PostConstruct
public void initializeCache() {
// Called once after all dependencies are injected
cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
System.out.println("Cache initialized");
}
@PreDestroy
public void clearCache() {
// Called before bean is destroyed (app shutdown)
cache.invalidateAll();
System.out.println("Cache cleared");
}
}
Lifecycle Summary Table
| Callback | When | Use Case |
|---|---|---|
| Constructor | First | Basic instantiation |
@Autowired |
After constructor | Dependency injection |
@PostConstruct |
After injection | Initialize resources, validate state |
afterPropertiesSet() |
After @PostConstruct
|
Alternative to @PostConstruct
|
@PreDestroy |
Before destruction | Release resources, cleanup |
destroy() |
After @PreDestroy
|
Alternative to @PreDestroy
|
Why Lifecycle Matters
| Scenario | Lifecycle Hook |
|---|---|
| Open database connections on startup | @PostConstruct |
| Start background thread/scheduler | @PostConstruct |
| Validate required configuration | @PostConstruct |
| Close connections on shutdown | @PreDestroy |
| Stop background threads gracefully | @PreDestroy |
| Flush caches/buffers | @PreDestroy |
Common Best Practices
- Prefer constructor injection for required dependencies (makes immutability and testing easier).
- Keep singleton beans stateless. If state is required, prefer narrower scopes.
- Use
@Configuration+@Beanfor explicit wiring when you need control over construction logic. - Avoid circular dependencies; if unavoidable, prefer setter injection for one side.
- Use
ObjectProvider/Providerfor injection of beans that should be created per-use. - Use
@PostConstruct/@PreDestroyfor lifecycle work and avoidInitializingBeanunless necessary.
Quick Checklist for Migrating Code to Spring-Managed Beans
- Identify hard
newcalls – consider making those beans. - Convert factory wiring to
@Configuration+@Beanor@Component+ constructor injection. - Decide appropriate scope (default singleton unless state or request/session semantics required).
- Add
@PostConstruct/@PreDestroyfor resource management. - Add unit tests by creating Spring test slices or mocking injected beans.
Comparison of Scopes
| Scope | Instance Per | Typical Use Case | Lifetime |
|---|---|---|---|
| Singleton | Application | Stateless services, utilities | Application lifecycle |
| Prototype | Each request or injection | Stateful or temporary objects | Per request/injection |
| Request | HTTP request | Request-specific logic | HTTP request lifecycle |
| Session | HTTP session | User-specific data | HTTP session lifecycle |
| Application | Servlet context | Global settings, resource pools | Servlet context lifecycle |