The Spring bean lifecycle step by step and why it matters at scale, ApplicationContext vs BeanFactory (and when to use each), circular dependencies and how Boot handles them, @Component vs @Service, JpaRepository vs CrudRepository, @Qualifier vs @Primary, @Transactional essentials, profiles and environment-variable injection.
Published September 25, 2026
At this level, Spring questions are about how the container behaves: the order of lifecycle callbacks, why proxies matter, and what Boot does when things conflict. Tie each answer to a production concern: startup failures, missing transactions, the wrong bean injected.
Short answer: For a singleton bean, the container:
BeanNameAware, ApplicationContextAware…).BeanPostProcessor.postProcessBeforeInitialization.@PostConstruct → InitializingBean.afterPropertiesSet() → a custom initMethod.postProcessAfterInitialization, where AOP proxies are created.SmartInitializingSingleton and ApplicationReadyEvent follow.@PreDestroy → DisposableBean.destroy() → a custom destroyMethod.Why it matters:
@PostConstruct calls the raw object, so @Transactional/@Async don't apply there yet.@PostConstruct slows every deployment. Use lazy initialisation, or ApplicationRunner, for optional warm-ups.@PreDestroy.Learn it in depth → Bean Lifecycle in Detail
ApplicationContext and BeanFactory?Short answer: BeanFactory is the basic IoC container. It creates beans lazily on request, and supports DI. ApplicationContext extends it, adding:
BeanPostProcessors and BeanFactoryPostProcessors, which is what makes @Autowired, @Value, AOP and @Transactional work;MessageSource (i18n);Environment/profiles.BeanFactory, and when ApplicationContext?Short answer: Use ApplicationContext in practically every application. Spring Boot always creates one. A bare BeanFactory makes sense only in very constrained environments, or in framework code that needs minimal, lazy bean management. Even then, you usually still interact with it through the context (context.getBeanFactory()), for example to register beans programmatically.
Short answer: Bean A needs bean B, and bean B (directly or through a chain) needs A. With constructor injection, Spring can't create either one first, so startup fails with BeanCurrentlyInCreationException ("Is there an unresolvable circular reference?"). It isn't a runtime deadlock. It's a creation-order problem detected at startup.
Key points to cover:
spring.main.allow-circular-references=true, but it's discouraged.Short answer: In order of preference:
ApplicationEventPublisher).@Lazy on one constructor parameter, or ObjectProvider<B>, resolved when first used.allow-circular-references=true. This works through Spring's early singleton references, but it's a legacy crutch.@Service
class OrderService {
private final ObjectProvider<InvoiceService> invoices; // resolved lazily, which breaks the creation cycle
OrderService(ObjectProvider<InvoiceService> invoices) { this.invoices = invoices; }
void complete(Order o) { invoices.getObject().issueFor(o); }
}
Common trap: presenting setter injection as "the fix". On modern Boot it doesn't even start without the opt-in flag, and the cycle usually means the classes have mixed responsibilities.
@Component and @Service? Are they interchangeable?Short answer: @Service is a specialisation of @Component (it's meta-annotated with it), so both register a bean through component scanning, and they're technically interchangeable. @Service communicates that the class is business-layer logic. That helps readers, AOP pointcuts (@within(org.springframework.stereotype.Service)) and architecture tests. (@Repository, unlike @Service, also adds exception translation.)
JpaRepository and CrudRepository, and when would you use CrudRepository?Short answer: The hierarchy is CrudRepository → ListCrudRepository → JpaRepository, with PagingAndSortingRepository alongside.
CrudRepository gives generic CRUD operations: save, findById, findAll, delete, count, existsById.JpaRepository adds JPA-specific operations: flush, saveAndFlush, batch deletes (deleteAllInBatch), getReferenceById, and List return types. It also includes paging and sorting, and query-by-example.When to prefer CrudRepository:
Repository interface, declaring only the methods you need.@Qualifier and @Primary? (And again: are @Component and @Service interchangeable?)Short answer:
@Primary marks the default bean when several beans match a type.@Qualifier("name") at the injection point explicitly chooses one, and it overrides @Primary.Use @Primary for "normally use this one", and @Qualifier for "here I specifically need that one". (On the second part: yes, @Component and @Service are interchangeable technically. @Service just documents the layer.)
@Bean @Primary PaymentGateway razorpay() { … }
@Bean PaymentGateway stripe() { … }
@Service
class RefundService {
RefundService(PaymentGateway gateway, // → razorpay (the primary)
@Qualifier("stripe") PaymentGateway international) { … } // → stripe
}
Key points to cover:
List<PaymentGateway>, Map<String, PaymentGateway>), and pick one at runtime: the strategy pattern.@Transactional used?Short answer: Put @Transactional on a public method of a Spring bean, usually in the service layer. Spring's proxy begins a transaction before the method, commits if it returns normally, and rolls back on RuntimeException or Error.
@Service
class CheckoutService {
@Transactional // one unit of work
public Order placeOrder(Cart cart) {
Order order = orders.save(Order.from(cart));
inventory.reserve(cart.items()); // throws → everything rolls back
return order;
}
@Transactional(readOnly = true)
public List<OrderSummary> history(long customerId) { … }
}
Key points to cover:
rollbackFor.@Transactional method from another method of the same bean) bypasses the proxy, so no transaction is started.REQUIRED (the default) joins an existing transaction, and REQUIRES_NEW suspends it and starts a new one. Isolation and timeout are configurable.Learn it in depth → @Transactional Deep Dive
Short answer: Profiles are named groups of configuration and beans (dev, test, prod) that are activated per environment. Beans can carry @Profile, and properties can live in application-{profile}.yml. Activate them with any of:
--spring.profiles.active=prod (a command-line argument);-Dspring.profiles.active=prod (a system property);SPRING_PROFILES_ACTIVE=prod (an environment variable, the usual choice in containers);@ActiveProfiles in tests.Key points to cover:
spring.profiles.group.prod=db,metrics) and spring.config.activate.on-profile inside multi-document YAML files.Short answer: Spring's Environment already includes the OS environment variables, so @Value("${DB_PASSWORD}") works. Better still, rely on relaxed binding: the environment variable PAYMENTS_BASE_URL automatically binds to the property payments.base-url, and so to a @ConfigurationProperties class.
@ConfigurationProperties(prefix = "payments")
record PaymentProperties(URI baseUrl, Duration timeout) { }
// export PAYMENTS_BASE_URL=https://api.pay.example PAYMENTS_TIMEOUT=3s
spring:
datasource:
password: ${DB_PASSWORD} # placeholder resolved from the environment
url: ${DB_URL:jdbc:postgresql://localhost:5432/shop} # with a default
Key points to cover:
/actuator/env (Boot masks sensitive keys by default).Q: What's the difference between BeanPostProcessor and BeanFactoryPostProcessor?
A: A BeanFactoryPostProcessor modifies bean definitions before any bean is created (for example, resolving property placeholders). A BeanPostProcessor modifies bean instances after creation (for example, wrapping them in AOP proxies, or processing @Autowired).
Q: Why doesn't @Transactional work on private methods?
A: Spring's proxy-based AOP can only intercept calls that go through the proxy, to methods it can override. Private methods can't be overridden, and they're called internally anyway.
Q: How do you run code after the application is fully started?
A: Use an ApplicationRunner or CommandLineRunner, or @EventListener(ApplicationReadyEvent.class). That's better than @PostConstruct for work that needs the whole context (and the server) to be ready.
Q: What does @DependsOn do?
A: It forces initialisation order when a bean relies on another's side effects rather than on a direct reference, for example a schema migrator that must run before a cache warmer. Prefer explicit dependencies where possible.