Test your Spring Framework knowledge with 20 interview questions covering IoC container, dependency injection, bean lifecycle, AOP, Spring MVC, transaction management, and Spring profiles.
Below are all 20 questions covered in this quiz, grouped by topic. Each question includes the correct answer and a detailed explanation to help you prepare for your next interview.
What is the Inversion of Control (IoC) principle in Spring?
A principle where the Spring container manages object creation, wiring, and lifecycle instead of the application code
Inversion of Control (IoC) is a fundamental principle in Spring where the container takes responsibility for creating objects, wiring dependencies, and managing their lifecycle. Instead of application code using 'new' to create dependencies, the container 'inverts' this control by injecting them. This promotes loose coupling and makes applications easier to test and maintain.
What is the difference between ApplicationContext and BeanFactory in Spring?
BeanFactory provides basic DI; ApplicationContext extends it with enterprise features like event publishing, internationalization, and eager bean initialization
BeanFactory is the root interface for the Spring IoC container, providing basic dependency injection. ApplicationContext extends BeanFactory and adds enterprise features: automatic BeanPostProcessor registration, event publication (ApplicationEvent), internationalization (MessageSource), and environment abstraction. ApplicationContext also eagerly initializes singleton beans at startup, while BeanFactory uses lazy initialization. In modern Spring applications, ApplicationContext is almost always used.
Which type of dependency injection is recommended by the Spring team and why?
```java
// Option A
@Component
public class OrderService {
@Autowired
private PaymentService paymentService;
}
// Option B
@Component
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
```Option B (constructor injection) because it enforces immutability, makes dependencies explicit, and supports easier unit testing
The Spring team officially recommends constructor injection. It makes dependencies explicit in the constructor signature, supports immutable objects via final fields, prevents the object from being created in a partially initialized state, and makes unit testing straightforward since you can pass mocks directly. Field injection hides dependencies, cannot enforce required dependencies at compile time, and makes testing harder since you need reflection to set private fields.
How do @Qualifier and @Primary resolve ambiguity when multiple beans of the same type exist?
```java
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource mainDataSource() { ... }
@Bean
public DataSource reportingDataSource() { ... }
}
```@Primary sets a default when no qualifier is specified; @Qualifier explicitly selects a specific bean by name, overriding @Primary
When Spring finds multiple beans of the same type, it throws a NoUniqueBeanDefinitionException unless the ambiguity is resolved. @Primary marks one bean as the default choice when no further qualification is provided. @Qualifier allows injection points to specify exactly which bean they want by name, and it takes precedence over @Primary. A common pattern is to mark the most commonly used implementation as @Primary and use @Qualifier at specific injection points that need a different implementation.
What does the @Lazy annotation do, and when should you use it?
It defers bean initialization until the bean is first requested, rather than at application startup
By default, Spring eagerly creates all singleton beans at startup. @Lazy defers the creation of a bean until it is first accessed. This is useful for expensive beans that may not be needed in every execution path, for breaking circular dependencies (as a temporary workaround), or for improving startup time. You can apply @Lazy on a @Bean method, a @Component class, or even on an @Autowired injection point. When used on an injection point, Spring injects a lazy-resolution proxy instead of the actual bean.
What is the difference between setter injection and constructor injection for optional dependencies?
```java
// Setter injection
@Component
public class ReportService {
private MetricsService metricsService;
@Autowired(required = false)
public void setMetricsService(MetricsService metricsService) {
this.metricsService = metricsService;
}
}
// Constructor injection
@Component
public class ReportService {
private final MetricsService metricsService;
public ReportService(Optional<MetricsService> metricsService) {
this.metricsService = metricsService.orElse(null);
}
}
```Setter injection with required=false naturally handles optional dependencies; constructor injection can use Optional<T> or @Nullable but makes the dependency less clearly optional
Setter injection is well-suited for optional dependencies because the bean can be fully constructed without them. Using @Autowired(required = false) means Spring will skip injection if the bean is not available. Constructor injection makes all parameters mandatory by default, but you can use java.util.Optional<T>, @Nullable, or @Autowired(required = false) on the constructor. The Spring team recommends constructor injection for mandatory dependencies and setter injection for optional ones. This distinction makes the code self-documenting about which dependencies are truly required.
What are the default bean scopes in Spring, and when would you use prototype scope?
The default is singleton (one instance per container); use prototype when you need a new instance every time the bean is requested, such as for stateful objects
Spring's default bean scope is singleton, meaning one shared instance per Spring IoC container. The prototype scope creates a new instance every time the bean is requested from the container. Use prototype for stateful beans that should not be shared, such as objects that hold user-specific data. Note that Spring does not manage the full lifecycle of prototype beans -- it creates them but does not call destroy methods. Web-aware scopes include request, session, and application.
What happens when a singleton-scoped bean depends on a prototype-scoped bean?
The prototype bean is injected once during singleton creation, so the same prototype instance is reused for every call, defeating the purpose of prototype scope
This is a classic Spring pitfall. When a singleton bean is created, its prototype dependency is injected once and never refreshed. Every subsequent use of the singleton reuses the same prototype instance. To get a fresh prototype instance each time, you can use method injection via @Lookup, inject an ObjectFactory<T> or Provider<T>, or use a scoped proxy with @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS).
What are request and session bean scopes, and what problem does the scoped proxy solve?
```java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
}
```Request scope creates a bean per HTTP request, session scope per HTTP session; a scoped proxy allows singleton beans to inject shorter-lived scoped beans by delegating to the correct instance at runtime
Request scope creates a new bean instance for each HTTP request, and session scope creates one per HTTP session. The problem arises when a singleton bean (like a controller) needs to use a session-scoped bean (like a shopping cart). Since the singleton is created once, it cannot directly hold a reference to a session-specific bean. A scoped proxy (ScopedProxyMode.TARGET_CLASS for classes, INTERFACES for interfaces) solves this by injecting a CGLIB proxy that delegates each method call to the correct instance for the current HTTP session or request. This proxy pattern is essential for mixing bean scopes in web applications.
What is the purpose of @PostConstruct and @PreDestroy annotations?
```java
@Component
public class CacheManager {
@PostConstruct
public void init() {
// load cache entries
}
@PreDestroy
public void cleanup() {
// flush and close cache
}
}
```@PostConstruct runs after dependency injection is complete; @PreDestroy runs before the bean is removed from the container during shutdown
@PostConstruct marks a method to be called after the bean's dependencies have been injected and all initialization is complete. It is ideal for setup logic that depends on injected beans. @PreDestroy marks a method to be called when the ApplicationContext is being shut down, allowing you to release resources like database connections or cache handles. The full bean lifecycle order is: constructor -> dependency injection -> @PostConstruct -> bean is ready -> @PreDestroy -> bean is destroyed. Alternatives include implementing InitializingBean/DisposableBean or using init-method/destroy-method in XML/Java config.
What is the complete lifecycle of a Spring bean from creation to destruction?
Constructor -> Dependency Injection -> BeanPostProcessor.postProcessBeforeInitialization -> @PostConstruct / InitializingBean.afterPropertiesSet -> BeanPostProcessor.postProcessAfterInitialization -> Ready -> @PreDestroy / DisposableBean.destroy
The full Spring bean lifecycle is: (1) Instantiation via constructor, (2) Dependency injection via setters/@Autowired, (3) BeanPostProcessor.postProcessBeforeInitialization, (4) @PostConstruct or InitializingBean.afterPropertiesSet or custom init-method, (5) BeanPostProcessor.postProcessAfterInitialization (this is where AOP proxies are created), (6) Bean is ready for use, (7) On container shutdown: @PreDestroy or DisposableBean.destroy or custom destroy-method. Understanding this lifecycle is crucial for debugging initialization issues and implementing custom bean processing.
What is a BeanPostProcessor and how does Spring use it internally?
```java
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// runs before @PostConstruct
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// runs after @PostConstruct; can return a proxy
return bean;
}
}
```BeanPostProcessor hooks into the bean lifecycle to inspect or modify beans during initialization; Spring uses it internally for AOP proxying, @Autowired processing, @Scheduled, and @Async
BeanPostProcessor is a powerful extension point in the Spring container. Its two methods are called for every bean during initialization: postProcessBeforeInitialization (before @PostConstruct) and postProcessAfterInitialization (after @PostConstruct). Spring uses this mechanism extensively: AutowiredAnnotationBeanPostProcessor handles @Autowired injection, AnnotationAwareAspectJAutoProxyCreator creates AOP proxies, ScheduledAnnotationBeanPostProcessor processes @Scheduled, and AsyncAnnotationBeanPostProcessor handles @Async. The postProcessAfterInitialization method can return a proxy wrapper, which is how AOP works in Spring.
What is the difference between @Configuration and @Component in Spring?
@Configuration uses CGLIB proxying to ensure @Bean methods return the same singleton instance when called internally; @Component does not proxy inter-bean method calls
@Configuration classes are enhanced at runtime with CGLIB proxying. This means if one @Bean method calls another @Bean method within the same class, Spring intercepts the call and returns the existing singleton instance from the container instead of creating a new object. With @Component (lite mode), @Bean methods are treated as plain factory methods, and calling one @Bean method from another creates a new instance each time, bypassing the container. Use @Configuration when beans have inter-dependencies defined in the same class.
What is Aspect-Oriented Programming (AOP) in Spring, and what problem does it solve?
A programming paradigm that separates cross-cutting concerns (logging, security, transactions) from business logic using aspects, pointcuts, and advice
AOP addresses cross-cutting concerns -- functionality that spans multiple modules and cannot be cleanly modularized with OOP alone (e.g., logging, security, transaction management, caching). In Spring AOP, an Aspect is a class containing advice (the action to take), and a Pointcut defines where the advice applies (e.g., all methods in a service layer). Spring AOP uses runtime proxies (JDK dynamic proxies or CGLIB) to weave advice into the target object's method execution without modifying the actual code.
What are the different types of AOP advice in Spring, and when is each executed?
```java
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint jp) { ... }
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))",
returning = "result")
public void logAfterReturn(JoinPoint jp, Object result) { ... }
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
// before
Object result = pjp.proceed();
// after
return result;
}
}
```@Before (before method), @AfterReturning (after successful return), @AfterThrowing (after exception), @After (after method regardless of outcome), and @Around (wraps the entire method execution)
Spring AOP defines five advice types: @Before runs before the target method. @AfterReturning runs after successful completion and can access the return value. @AfterThrowing runs if the method throws an exception and can access the exception. @After (finally advice) runs after the method regardless of outcome. @Around is the most powerful -- it wraps the entire method execution, controls whether to proceed, can modify arguments and return values, and can handle exceptions. @Around is commonly used for performance monitoring, caching, and transaction management.
Why does the following Spring AOP configuration fail to intercept the method call?
```java
@Service
public class OrderService {
public void createOrder(Order order) {
// business logic
this.validateOrder(order); // internal call
}
@Cacheable("validations")
public void validateOrder(Order order) {
// expensive validation
}
}
```Internal method calls (this.validateOrder) bypass the Spring AOP proxy, so the @Cacheable advice is never applied
Spring AOP uses proxy-based interception. When an external call reaches OrderService, it goes through the proxy, which applies aspects like @Cacheable. However, when createOrder() calls this.validateOrder() internally, it bypasses the proxy and calls the method directly on the target object, so no aspect is applied. Solutions include: (1) extracting validateOrder into a separate bean, (2) injecting the service into itself (self-injection), (3) using ApplicationContext to get the proxied instance, or (4) using AspectJ compile-time or load-time weaving instead of Spring AOP proxies.
What is the flow of a request through Spring MVC from the browser to the controller and back?
Browser -> DispatcherServlet -> HandlerMapping -> Controller -> ViewResolver -> View -> Response
The Spring MVC request flow is: (1) The browser sends a request to the DispatcherServlet (front controller). (2) DispatcherServlet consults HandlerMapping to find the appropriate controller. (3) The request is passed through HandlerInterceptors (preHandle). (4) The controller processes the request and returns a ModelAndView or response body. (5) For view-based responses, the ViewResolver resolves the logical view name to an actual view. (6) The view renders the response. For REST APIs with @ResponseBody or @RestController, steps 5-6 are replaced by HttpMessageConverters that serialize the return value (e.g., to JSON via Jackson).
How does @Transactional propagation work? What happens when a transactional method calls another transactional method?
```java
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
paymentService.processPayment(order);
}
}
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment(Order order) { ... }
}
```With REQUIRES_NEW, the current transaction is suspended and a brand new independent transaction is created for processPayment; if processPayment fails, placeOrder's transaction can still commit
Spring supports seven propagation levels. REQUIRED (default) joins the existing transaction or creates one if none exists. REQUIRES_NEW always suspends the current transaction and creates a new independent one. MANDATORY requires an existing transaction and throws an exception if none exists. SUPPORTS joins an existing transaction but runs non-transactionally if none exists. NOT_SUPPORTED suspends any existing transaction. NEVER throws an exception if a transaction exists. NESTED creates a savepoint within the existing transaction. In the example, processPayment runs in its own transaction, so its commit/rollback is independent of placeOrder.
Why might @Transactional not work as expected on a method? Consider:
```java
@Service
public class UserService {
@Transactional
public void updateUser(User user) {
try {
userRepository.save(user);
emailService.sendNotification(user);
} catch (Exception e) {
log.error("Failed to send email", e);
}
}
}
```The catch block swallows the exception, so Spring does not see a runtime exception and will commit the transaction even if emailService threw one; also @Transactional only rolls back on unchecked exceptions by default
There are several common pitfalls with @Transactional: (1) Catching exceptions inside the method prevents Spring from seeing the exception, so the transaction commits even on failure. (2) By default, @Transactional only rolls back on unchecked exceptions (RuntimeException subclasses), not checked exceptions. Use rollbackFor = Exception.class to include checked exceptions. (3) @Transactional on private methods is ignored because the proxy cannot intercept them. (4) Self-invocation (calling a @Transactional method from within the same class) bypasses the proxy. Always ensure exceptions propagate and methods are public.
How do Spring profiles work, and how can you activate them?
```java
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource dataSource() {
// production database
}
}
```Profiles allow environment-specific bean registration; activate via spring.profiles.active property, SPRING_PROFILES_ACTIVE env variable, JVM arg, or programmatically via ConfigurableEnvironment
Spring profiles provide a way to segregate bean definitions for different environments. Beans annotated with @Profile are only registered when that profile is active. You can activate profiles via: (1) spring.profiles.active in application.properties/yml, (2) SPRING_PROFILES_ACTIVE environment variable, (3) -Dspring.profiles.active=prod JVM argument, (4) programmatically via ConfigurableEnvironment.setActiveProfiles(). You can also use profile expressions like @Profile("!production") or @Profile({"dev", "test"}). Spring Boot additionally supports profile-specific config files like application-prod.yml.
Take the interactive quiz and get your score with a personalized topic breakdown.
Start the Quiz20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Java20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
Spring Boot20 questions · 30 min
DevOps20 questions · 30 min
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.