Definition
- Factory –
ApplicationContextandBeanFactorycreate and wire beans, so you never callnewon a service class. - Singleton – every Spring bean is one shared instance per container by default, enforcing stateless design.
- Proxy – Spring AOP and
@Transactionalinject a proxy that wraps your method with begin/commit/rollback logic. - Template Method –
JdbcTemplate,RestTemplate, andJmsTemplatedefine the fixed skeleton and let you plug in one varying step. - Observer –
ApplicationEventand@EventListenerlet listeners react to published events without tight coupling. - Strategy, Iterator, Decorator –
Comparatorswaps comparison logic, every collection returns anIterator, andjava.iostreams layer behaviour with decoration.
Imagine the National Bank of India's software is like the bank building itself. The container that hires and manages every employee is the Factory. Each manager role has exactly one person on duty: that is the Singleton. When a customer meets a "manager", they are often meeting a stand-in who checks their ID first: that stand-in is the Proxy, exactly how @Transactional works. Rajesh does not build any of this; it is already wired into the building he walks into.
Understanding the Concepts
Where does Spring use the Factory pattern?
Spring's ApplicationContext and BeanFactory are a textbook Factory. You ask the container for a bean and it decides how to construct it, wire its dependencies, and hand it back. You never call new TransferService(...), so the creation detail is centralised.
package com.bank;
import com.bank.service.TransferService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class FactoryDemo {
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(BankConfig.class);
TransferService svc = ctx.getBean(TransferService.class);
svc.transfer("RAJESH-001", "PRIYA-002",
new java.math.BigDecimal("50000"));
}
}
The intent is exactly the Factory pattern: the caller asks for an object by type, and the factory decides how to build it.
Where does Spring use the Singleton pattern?
By default, every Spring bean is a Singleton within the container. There is one shared TransferService instance for the whole application. This is Spring's own managed Singleton, distinct from the classic static-field Singleton.
package com.bank.service;
import org.springframework.stereotype.Service;
@Service
public class TransferService {
// One instance shared across the entire application.
// Must be stateless: no mutable instance fields.
}
Because the instance is shared, services must be stateless. If you need per-request state, use the prototype or request scope instead.
Where does Spring use the Proxy pattern?
Spring AOP, @Transactional, and lazy beans all use the Proxy pattern. When you annotate a method with @Transactional, Spring does not inject your raw class. It injects a proxy that opens a transaction, calls your method, then commits or rolls back.
package com.bank.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
public class TransferService {
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
// proxy: BEGIN TRANSACTION
accountRepository.debit(from, amount);
accountRepository.credit(to, amount);
// proxy: COMMIT (or ROLLBACK on exception)
}
}
A common bug: calling a @Transactional method from another method in the same class bypasses the proxy, so the transaction silently never starts. Knowing the Proxy pattern explains why.
Where does Spring use the Template Method pattern?
JdbcTemplate, RestTemplate, and JmsTemplate define the fixed skeleton: acquire connection, run work, handle exceptions, release connection. You fill the one varying step through a callback.
package com.bank.repository;
import org.springframework.jdbc.core.JdbcTemplate;
import com.bank.model.Account;
import java.math.BigDecimal;
import java.util.List;
public class AccountRepository {
private final JdbcTemplate jdbc;
public AccountRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public List<Account> findAll() {
return jdbc.query(
"SELECT id, balance FROM accounts",
(rs, i) -> new Account(
rs.getString("id"),
rs.getBigDecimal("balance")));
}
}
The RowMapper you pass is the variable step. The template owns the boilerplate of connection management and exception translation.
Where does Spring use the Strategy pattern?
Strategy shows up where behaviour is pluggable. Spring Security picks among many AuthenticationProvider implementations at runtime. Spring's Resource loaders choose how to load a classpath versus a URL resource.
// Spring Security: multiple authentication strategies
@Bean
public AuthenticationManager authManager(
DaoAuthenticationProvider daoProvider,
LdapAuthenticationProvider ldapProvider) {
return new ProviderManager(List.of(daoProvider, ldapProvider));
}
Where does Spring use the Observer pattern?
Spring's ApplicationEvent and @EventListener implement Observer. You publish an event and every listener reacts independently.
package com.bank.event;
import java.math.BigDecimal;
public class TransferCompletedEvent {
private final String transactionId;
private final BigDecimal amount;
public TransferCompletedEvent(String transactionId, BigDecimal amount) {
this.transactionId = transactionId;
this.amount = amount;
}
public String getTransactionId() { return transactionId; }
public BigDecimal getAmount() { return amount; }
}
package com.bank.listener;
import com.bank.event.TransferCompletedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class AuditListener {
@EventListener
public void onTransferCompleted(TransferCompletedEvent event) {
System.out.println("Audit: Transfer " + event.getTransactionId()
+ " of " + event.getAmount() + " recorded");
}
}
Adding a new reaction, such as loyalty points, is a new @EventListener class. The transfer logic is never touched.
Where does Spring use the Decorator and Adapter patterns?
Decorator shows up in BeanPostProcessor, which wraps beans to add behaviour, and in servlet request/response wrappers. Adapter appears as Spring MVC's HandlerAdapter, which lets the dispatcher invoke many kinds of handlers through one interface.
// BeanPostProcessor: wraps beans after creation (Decorator)
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String name) {
System.out.println("Bean created: " + name);
return bean; // could return a decorated proxy
}
}
Where does the JDK use design patterns?
The JDK is full of patterns:
- Iterator – every collection returns an
Iteratorviaiterator(), which is the Iterator pattern. - Strategy –
Comparatorswaps the comparison algorithm:list.sort(Comparator.comparing(Account::getBalance)). - Decorator –
java.iostreams:new BufferedReader(new InputStreamReader(new FileInputStream(f)))layers buffering over decoding over raw bytes. - Command –
RunnableandCallableare actions packaged as objects for later execution. - Builder –
StringBuilder,UriComponentsBuilder.fromUriString("/api").queryParam("id", 1).build().
package com.bank;
import com.bank.model.Account;
import java.math.BigDecimal;
import java.util.*;
public class JdkPatternDemo {
public static void main(String[] args) {
List<Account> accounts = List.of(
new Account("RAJESH-001", new BigDecimal("50000")),
new Account("PRIYA-002", new BigDecimal("120000")));
// Strategy: Comparator swaps the comparison logic
List<Account> sorted = new ArrayList<>(accounts);
sorted.sort(Comparator.comparing(Account::getBalance));
// Iterator: every collection provides one
Iterator<Account> it = sorted.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
Why does recognizing these matter in practice?
Recognizing patterns in frameworks proves you understand them operationally, not as textbook definitions. Practical consequences at National Bank of India:
- Knowing
@Transactionalis a Proxy explains why self-invocation skips the transaction. - Knowing beans are Singletons tells Rajesh to keep services stateless.
- Knowing events are Observer lets Pankaj add a new audit reaction without touching transfer logic.
- Knowing
JdbcTemplateis Template Method tells the team to use callbacks, not to subclass the template.
What should a senior engineer know?
- Proxy limitations. CGLIB proxies cannot intercept
finalmethods. JDK dynamic proxies require an interface. This affects which classes can use@Transactional. - Singleton thread safety. Since beans are shared, any mutable state must be synchronised or avoided entirely.
- Event ordering. Spring
@EventListenerdoes not guarantee ordering across listeners unless you use@Orderor@TransactionalEventListener. - Template Method vs Strategy in Spring.
JdbcTemplateis Template Method (fixed skeleton + pluggable callback), whileAuthenticationProvideris Strategy (interchangeable implementations). Knowing the difference helps you decide which pattern to apply in your own code.
What are common interview follow-up questions?
- Why does self-invocation bypass
@Transactional? - What is the difference between Spring's managed Singleton and the classic static Singleton?
- How does
JdbcTemplateuse Template Method internally? - Name three patterns in the JDK and show where they appear.
- How would you add a new event listener without changing existing code?
Visual Overview
Spring IoC Container (Factory + Singleton) ┌──────────────────────────────────────────────┐ │ @Service TransferService (Singleton bean) │ │ │ │ │ │ wrapped by │ │ ▼ │ │ Proxy (AOP / @Transactional) │ │ │ │ │ │ BEGIN → call → COMMIT / ROLLBACK │ │ ▼ │ │ JdbcTemplate (Template Method) │ │ │ │ │ │ acquire conn → callback → release │ │ ▼ │ │ ApplicationEvent (Observer) │ │ │ │ │ ├──→ AuditListener │ │ ├──→ NotificationListener │ │ └──→ LoyaltyListener │ └──────────────────────────────────────────────┘
JDK Patterns: Iterator: Collection.iterator() → hasNext() / next() Strategy: list.sort(Comparator.comparing(Account::getBalance)) Decorator: BufferedReader → InputStreamReader → FileInputStream Command: Runnable / Callable → execute later Builder: StringBuilder.append().append().toString()
Key Takeaway
Spring and the JDK are not separate from design patterns; they are built out of them, so every developer uses patterns daily whether they name them or not. Recognising where the framework uses a pattern is what proves practical understanding rather than textbook memorisation.
In Spring, the container itself is a Factory that creates and wires beans, which is why you never call new on a service. Those beans are Singletons by default, meaning one shared instance per container, which is why stateless services are the norm. Anything with @Transactional works through a dynamic Proxy that wraps the method to begin and commit the transaction. The template classes like JdbcTemplate are Template Method: they own the fixed skeleton of connect, execute, and clean up, and let you plug in the one varying step through a callback. Events use the Observer pattern through ApplicationEvent and @EventListener, and pluggable authentication providers are Strategy.
In the JDK, every collection hands back an Iterator. Comparator is the clearest Strategy because it lets you swap the comparison algorithm into a fixed sort. The java.io streams are the canonical Decorator, where a BufferedReader wraps an InputStreamReader which wraps a FileInputStream, each layer adding behaviour. And Runnable and Callable are Command objects.
Why this matters at National Bank of India is concrete. Knowing that @Transactional is a Proxy explains the real bug where calling a transactional method from the same class bypasses the proxy and the transaction silently never starts. Knowing the container is a Factory and beans are Singletons tells Rajesh to keep services stateless. And knowing events are Observer lets Pankaj add a new audit reaction by adding a listener without ever touching the transfer logic.
Remember one sentence: Spring and the JDK are patterns in disguise, and recognising them is what separates reading a framework fluently from treating it as a black box.
---