Bean Management & Lifecycle
01 What are bean scopes?
Solution and explanation
What it is
A bean's scope controls how many instances Spring creates and how long they live.
The scopes
- singleton (default): one instance per Spring container, shared everywhere.
- prototype: a new instance every time the bean is requested or injected.
- request: one instance per HTTP request (web apps).
- session: one instance per HTTP session.
- application: one instance per ServletContext.
- websocket: one instance per WebSocket session.
@Service // singleton by default
public class JobService { }
@Component
@Scope("prototype")
public class ResumeParser { } // new parser each time
@Component
@RequestScope
public class RequestContext { // per HTTP request
private String requestId = UUID.randomUUID().toString();
public String id() { return requestId; }
}
Showing the difference
ApplicationContext ctx = SpringApplication.run(App.class, args);
System.out.println(ctx.getBean(JobService.class) == ctx.getBean(JobService.class)); // true
System.out.println(ctx.getBean(ResumeParser.class) == ctx.getBean(ResumeParser.class)); // false
Injecting a shorter-lived bean into a singleton
A singleton is created once, so injecting a prototype or request bean directly freezes one instance forever. Solutions:
- Inject ObjectProvider<ResumeParser> and call getObject() each time.
- Use a scoped proxy: @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) (Spring injects a proxy that finds the current request's instance).
@Service
public class ImportService {
private final ObjectProvider<ResumeParser> parsers;
public ImportService(ObjectProvider<ResumeParser> parsers) { this.parsers = parsers; }
public void importAll(List<File> files) {
files.forEach(f -> parsers.getObject().parse(f)); // fresh parser per file
}
}
Lifecycle differences
- Singleton: created at startup (eagerly), destroyed at shutdown (@PreDestroy runs).
- Prototype: created on demand, Spring does not track it afterwards – @PreDestroy is not called.
Note: most services, repositories and controllers should be stateless singletons.
@RequestScope
@Component
public class RequestAuditContext {
}
Compare singleton and prototype beans by creating multiple container lookups.
Quick check
Which statement is correct?
02 What is a singleton bean?
Solution and explanation
What it is
A singleton bean is the default scope: Spring creates exactly one instance per application context and injects that same instance everywhere it is needed.
@Service
public class ExchangeRateService {
public BigDecimal usdToInr() { return new BigDecimal("83.20"); }
}
@RestController class A { A(ExchangeRateService s) { System.out.println(s); } }
@RestController class B { B(ExchangeRateService s) { System.out.println(s); } }
com.portal.ExchangeRateService@4a1c0752
com.portal.ExchangeRateService@4a1c0752 ← same object
Spring singleton vs GoF Singleton pattern
- GoF Singleton: one instance per class per JVM (ClassLoader), enforced with a private constructor.
- Spring singleton: one instance per container per bean definition; you can still create others with new or define two beans of the same class.
Thread safety
A web server handles requests on many threads at once, and all of them use the same singleton. Spring does not make it thread-safe for you.
@Service
public class CounterService {
private int count = 0; // ✗ shared mutable state → race conditions
public int next() { return ++count; }
}
@Service
public class SafeCounterService {
private final AtomicInteger count = new AtomicInteger(); // ✓ thread-safe
public int next() { return count.incrementAndGet(); }
}
Best practice: keep singletons stateless – only final dependencies and configuration as fields; per-request data lives in method parameters and local variables.
Eager vs lazy creation
Singletons are created at startup so configuration errors fail fast. @Lazy delays creation until first use (useful for heavy, rarely used beans).
Note: storing the current user, request data or a running total in a singleton's fields is one of the most common production bugs in Spring applications.
@Service
public class JobSearchService {
public List<Job> search(String keyword) {
// Do not store request-specific state here.
return List.of();
}
}
Send concurrent requests and verify that request-specific state is not stored in a singleton service.
Quick check
Which statement is correct?
03 What is a prototype bean?
Solution and explanation
What it is
A prototype-scoped bean gets a new instance every time it is requested from the container or injected. Use it for stateful, short-lived objects.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportBuilder {
private final List<String> rows = new ArrayList<>(); // state belongs to one report
public ReportBuilder add(String row) { rows.add(row); return this; }
public String build() { return String.join("\n", rows); }
}
ReportBuilder a = ctx.getBean(ReportBuilder.class);
ReportBuilder b = ctx.getBean(ReportBuilder.class);
System.out.println(a == b); // false
The singleton-injection pitfall
@Service
public class ReportService {
private final ReportBuilder builder; // ✗ injected once → shared by every call
public ReportService(ReportBuilder builder) { this.builder = builder; }
}
Fix with a provider (or @Lookup method):
@Service
public class ReportService {
private final ObjectProvider<ReportBuilder> builders;
public ReportService(ObjectProvider<ReportBuilder> builders) { this.builders = builders; }
public String monthly(List<String> rows) {
ReportBuilder builder = builders.getObject(); // new instance each call
rows.forEach(builder::add);
return builder.build();
}
}
Lifecycle
- Spring creates and injects the bean and runs @PostConstruct.
- After that, the caller owns it: Spring does not call @PreDestroy for prototypes. Release resources yourself.
When to use
Builders, parsers or clients holding per-task state, or objects that are not thread-safe. Often a plain new inside a method is simpler – use prototype scope when the object itself needs injected dependencies.
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class ImportWorkspace {
}
Request two prototype instances and verify that they are different objects.
Quick check
Which statement is correct?
04 What is @Primary?
Solution and explanation
What it is
@Primary marks one bean as the default choice when several beans of the same type qualify for injection and no @Qualifier is specified.
public interface NotificationSender { void send(String to, String message); }
@Component
@Primary
public class EmailSender implements NotificationSender {
public void send(String to, String msg) { System.out.println("Email to " + to); }
}
@Component("sms")
public class SmsSender implements NotificationSender {
public void send(String to, String msg) { System.out.println("SMS to " + to); }
}
@Service
public class ApplicationAlerts {
private final NotificationSender sender; // EmailSender (primary)
public ApplicationAlerts(NotificationSender sender) { this.sender = sender; }
}
@Service
public class OtpService {
private final NotificationSender sender; // SmsSender (explicit)
public OtpService(@Qualifier("sms") NotificationSender sender) { this.sender = sender; }
}
On @Bean methods
@Configuration
public class DataSourceConfig {
@Bean @Primary
DataSource portalDataSource() { ... } // used by JPA by default
@Bean
DataSource reportingDataSource() { ... } // injected with @Qualifier("reportingDataSource")
}
Rules
- Only one @Primary per type, otherwise ambiguity returns.
- @Qualifier at an injection point overrides @Primary.
Note: @Primary is ideal for "one normal implementation plus special cases"; when every consumer must choose deliberately, prefer @Qualifier everywhere.
@Primary
@Component
public class DefaultTaxCalculator
implements TaxCalculator {
}
Register default and international tax calculators and inject each implementation intentionally.
Quick check
Which statement is correct?
05 What is @Configuration?
Solution and explanation
What it is
@Configuration marks a class as a source of bean definitions. Its @Bean methods create and configure objects – usually third-party classes you cannot annotate with @Component – and register them in the container.
@Configuration
public class HttpClientConfig {
@Bean
public RestClient razorpayClient(@Value("${razorpay.base-url}") String baseUrl) {
return RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader("Accept", "application/json")
.build();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
Full mode: proxying guarantees singletons
@Configuration classes are proxied with CGLIB, so calling one @Bean method from another returns the container's single instance instead of creating a new object.
@Configuration
public class AppConfig {
@Bean public JobRepository jobRepository() { return new JobRepository(); }
@Bean public JobService jobService() { return new JobService(jobRepository()); } // same instance
@Bean public ReportService reportService() { return new ReportService(jobRepository()); }
}
With @Configuration(proxyBeanMethods = false) (lite mode), those inner calls would create new objects – faster startup, but inject dependencies as method parameters instead.
Organising configuration
- One configuration class per concern: security, data source, messaging, web.
- @Import(OtherConfig.class) combines them.
- @Profile("prod") activates a configuration only in some environments.
- @ConditionalOnProperty enables features by property.
@Configuration
@Profile("dev")
public class DevDataConfig {
@Bean CommandLineRunner seed(JobRepository jobs) { return args -> jobs.save(new Job("Sample Java job")); }
}
Note: @SpringBootApplication itself is a @Configuration, so small apps can declare @Bean methods in the main class.
@Configuration
public class ClientConfig {
@Bean
public Clock clock() {
return Clock.systemUTC();
}
}
Create a configuration class that exposes a Clock bean and inject it into a service.
Quick check
Which statement is correct?
06 What is @Bean?
Solution and explanation
What it is
@Bean marks a method inside a @Configuration (or @Component) class whose return value Spring registers as a bean. You control exactly how the object is created.
@Configuration
public class MailConfig {
@Bean(name = "transactionalMailer", initMethod = "connect", destroyMethod = "close")
public SmtpClient smtpClient(MailProperties props) { // dependencies are method parameters
SmtpClient client = new SmtpClient(props.host(), props.port());
client.setTimeout(Duration.ofSeconds(10));
return client;
}
}
Details
- The bean name defaults to the method name (smtpClient); override with @Bean(name = ...).
- Method parameters are resolved from the container (like constructor injection).
- initMethod and destroyMethod hook into the lifecycle (Spring also auto-detects close()/shutdown()).
- Combine with @Scope, @Primary, @Lazy, @Profile and @Conditional*.
@Bean vs @Component
- @Component: you own the class; Spring discovers it by scanning.
- @Bean: for classes you cannot modify (libraries: RestClient, ObjectMapper, DataSource, KafkaTemplate customisations), or when creation needs logic.
@Bean
@ConditionalOnProperty(name = "payments.provider", havingValue = "stripe")
public PaymentGateway stripeGateway(StripeProperties p) { return new StripeGateway(p.apiKey()); }
@Bean
@ConditionalOnProperty(name = "payments.provider", havingValue = "razorpay", matchIfMissing = true)
public PaymentGateway razorpayGateway(RazorpayProperties p) { return new RazorpayGateway(p.key(), p.secret()); }
Note: overriding a Boot default is usually just declaring your own @Bean of that type – auto-configuration backs off.
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.findAndAddModules()
.build();
}
}
Configure and inject a third-party ObjectMapper using a @Bean method.
Quick check
Which statement is correct?
07 What is component scanning?
Solution and explanation
What it is
Component scanning is how Spring automatically finds classes annotated with @Component, @Service, @Repository, @Controller, @RestController and @Configuration, and registers them as beans – without listing each one manually.
How it works
- @ComponentScan (included in @SpringBootApplication) defines base packages – by default the package of the annotated class.
- Spring scans those packages and sub-packages on the classpath.
- Every class with a stereotype annotation becomes a bean definition.
com.ascendia.portal ← @SpringBootApplication here
├── jobs/JobService.java ✓ scanned
├── companies/CompanyService.java ✓ scanned
└── web/JobController.java ✓ scanned
com.ascendia.shared/AuditLogger ✗ outside the base package
Customising the scan
@SpringBootApplication(scanBasePackages = {"com.ascendia.portal", "com.ascendia.shared"})
public class PortalApplication { }
@Configuration
@ComponentScan(basePackages = "com.ascendia.plugins",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Legacy.*"))
public class PluginConfig { }
Common problems
- "No qualifying bean of type X": X is outside the scanned packages or missing an annotation.
- Main class placed in a sub-package, shrinking the scan area.
- Scanning too broadly (e.g. the root "com") slows startup and picks up unintended beans.
Alternatives
- @Import(SomeConfig.class) to bring specific configurations in.
- Auto-configuration for library code – libraries should not rely on the application scanning them.
Note: Spring Data repositories are found by a separate scan enabled by auto-configuration (@EnableJpaRepositories), also defaulting to the main package.
@ComponentScan({
"com.ascendia.jobs",
"com.ascendia.shared"
})
public class ApplicationConfig {
}
Move a service outside the scanned package, observe the failure and correct the package configuration.
Quick check
Which statement is correct?
08 What is the bean lifecycle?
Solution and explanation
The phases
- Instantiation – Spring calls the constructor.
- Populate properties – dependencies are injected (constructor, setters, fields).
- Aware callbacks – BeanNameAware, ApplicationContextAware, etc. if implemented.
- BeanPostProcessor "before initialization".
- Initialization – @PostConstruct, then InitializingBean.afterPropertiesSet(), then a custom initMethod.
- BeanPostProcessor "after initialization" – proxies for @Transactional, @Async, @Cacheable are created here.
- Bean is ready and used by the application.
- Destruction (singletons, on shutdown) – @PreDestroy, then DisposableBean.destroy(), then destroyMethod.
constructor → inject dependencies → @PostConstruct → (proxy) → in use → @PreDestroy
Example
@Component
public class ExchangeRateCache implements InitializingBean, DisposableBean {
private final RateClient client;
private Map<String, BigDecimal> rates = Map.of();
public ExchangeRateCache(RateClient client) { // 1–2
this.client = client;
System.out.println("1. constructor");
}
@PostConstruct
void warmUp() { // 5
rates = client.fetchAll();
System.out.println("2. @PostConstruct – loaded " + rates.size() + " rates");
}
@Override public void afterPropertiesSet() { System.out.println("3. afterPropertiesSet"); }
@PreDestroy
void flush() { System.out.println("4. @PreDestroy"); } // 8
@Override public void destroy() { System.out.println("5. destroy"); }
}
1. constructor
2. @PostConstruct – loaded 160 rates
3. afterPropertiesSet
... application runs ...
4. @PreDestroy
5. destroy
Why it matters
- Do initialisation that needs injected dependencies in @PostConstruct, not the constructor's field defaults.
- @Transactional does not apply inside @PostConstruct, because the proxy is created after initialization.
- Prototype beans never get destruction callbacks.
Note: for work that should run after the whole application is ready (cache warm-up, scheduled jobs), prefer ApplicationRunner or @EventListener(ApplicationReadyEvent.class).
@PostConstruct
void initialize() {
}
@PreDestroy
void close() {
}
Log the lifecycle phases of a bean during application startup and shutdown.
Quick check
Which statement is correct?
09 What is @PostConstruct?
Solution and explanation
What it is
@PostConstruct marks a method that Spring calls once, after the bean is created and all dependencies have been injected, but before the bean is used. It is the place for initialisation logic.
@Service
public class SkillDictionary {
private final SkillRepository skills;
private Set<String> known = Set.of();
public SkillDictionary(SkillRepository skills) { this.skills = skills; }
@PostConstruct
void load() {
known = skills.findAllNames().stream().map(String::toLowerCase).collect(Collectors.toSet());
log.info("Loaded {} skills", known.size());
}
public boolean isKnown(String skill) { return known.contains(skill.toLowerCase()); }
}
Rules
- The method has no parameters, returns void and may have any visibility.
- It runs exactly once per bean instance.
- If it throws, the bean fails to create and application startup fails – useful for validating configuration early.
Typical uses
- Loading reference data or warming a cache.
- Validating required configuration (missing API key → fail fast).
- Opening a connection or registering a listener.
Caveats
- Proxies are not applied yet, so @Transactional/@Async on the same bean are not active inside it.
- Long work here delays startup; consider ApplicationReadyEvent or a background job for heavy loading.
- In Spring Boot 3 the annotation is jakarta.annotation.PostConstruct (previously javax).
Note: why not the constructor? In the constructor, field and setter injection have not happened yet; @PostConstruct is guaranteed to see every dependency.
@PostConstruct
void validateConfiguration() {
Assert.hasText(
apiUrl,
"apiUrl is required"
);
}
Validate one required application property during bean initialization.
Quick check
Which statement is correct?
10 What is @PreDestroy?
Solution and explanation
What it is
@PreDestroy marks a method that Spring calls just before a bean is removed from the container – normally during application shutdown. Use it to release resources cleanly.
@Component
public class ResumeIndexWriter {
private final ExecutorService pool = Executors.newFixedThreadPool(4);
private final BufferedWriter log;
public ResumeIndexWriter() throws IOException {
log = Files.newBufferedWriter(Path.of("index.log"));
}
@PreDestroy
void shutdown() throws Exception {
pool.shutdown(); // stop accepting tasks
if (!pool.awaitTermination(20, TimeUnit.SECONDS)) pool.shutdownNow();
log.flush();
log.close();
System.out.println("Index writer closed cleanly");
}
}
When it runs
- On a normal shutdown: context.close(), Ctrl+C, SIGTERM from Docker/Kubernetes (Spring registers a JVM shutdown hook).
- Not on a hard kill (kill -9) or a JVM crash.
- Only for singletons; prototype beans are not tracked after creation.
Typical uses
- Closing thread pools, file handles, sockets and clients.
- Flushing buffered metrics or logs.
- Deregistering from a service registry.
Graceful shutdown of the web server
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
With these, in-flight HTTP requests finish before beans are destroyed.
Note: libraries such as HikariCP and Kafka clients already close themselves through their own destroy methods; you only need @PreDestroy for resources you create.
@PreDestroy
void shutdown() {
executor.shutdown();
}
Create a managed executor and shut it down when the Spring context closes.
Quick check
Which statement is correct?
Spring Quiz: Lessons 1 – 10
Test what you have learned before continuing to the next learning block.
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
Which statement is correct?
