Skip to content

Patterns in Spring

Spring uses and enables many patterns, but an annotation is not itself a design pattern. Understanding the collaboration and runtime mechanism prevents cargo- cult use of stereotypes.

Pattern map

Spring mechanism Related pattern or principle Important distinction
IoC container and constructor injection Dependency Injection / IoC DI is not the same as Dependency Inversion
BeanFactory and FactoryBean Factory A FactoryBean creates another exposed object
Bean scopes Managed lifecycle Spring singleton is per bean definition per container
AOP advice Proxy / interceptor chain Calls must pass through the applicable proxy
JdbcTemplate and similar helpers Template plus callback strategy Resource/error workflow is fixed; operation varies
Spring MVC DispatcherServlet Front Controller One entry coordinates request dispatch
Handler adapters Adapter Different handler styles fit one dispatch process
Application events Observer In-process events are not a durable message broker
Spring Data repositories Repository Persistence access is expressed as a collection-like role
Security filter chain Chain of Responsibility / intercepting filter Order and short-circuit behavior matter

Dependency Injection

Spring's container creates beans and supplies collaborators. Constructor injection makes required dependencies explicit and supports fully initialized, immutable component references.

@Service
final class PlaceOrder {
    private final OrderRepository orders;
    private final PricingPolicy pricing;

    PlaceOrder(OrderRepository orders, PricingPolicy pricing) {
        this.orders = orders;
        this.pricing = pricing;
    }
}

The pattern does not require an interface for every class. Introduce an abstraction when clients need a stable role, multiple implementations, an architectural boundary, or a meaningful test substitute.

Singleton scope is not the GoF Singleton

The default Spring singleton scope asks one container to manage one instance for a bean definition. The class need not have a private constructor or global getInstance() method, and another application context can manage another instance. Singleton-scoped beans must not store unsynchronized request-specific mutable state.

Proxy-based cross-cutting behavior

Spring AOP can apply transactions, caching, method security, async execution, and custom advice through proxies. Depending on configuration and target type, Spring uses interface-based or class-based proxies.

sequenceDiagram
    participant Caller
    participant Proxy
    participant Advice
    participant Target
    Caller->>Proxy: method call
    Proxy->>Advice: before / around
    Advice->>Target: proceed
    Target-->>Advice: result or exception
    Advice-->>Proxy: transformed outcome
    Proxy-->>Caller: result or exception

In ordinary proxy mode, self-invocation does not cross the proxy, so calling one method on this does not activate separate advice declared only on the invoked method. Private methods cannot be advised through subclass overriding, and final types/methods constrain class-based proxies. Design and test transaction, security, caching, and async boundaries as behavior—not as decorative annotations. For caching specifically, key design, freshness, invalidation, capacity, and stampede handling remain explicit application-cache decisions.

Template and callback

Template-style Spring APIs centralize repeated workflow such as resource acquisition, cleanup, and exception translation. A callback, lambda, or strategy provides the operation-specific step. This differs from the inheritance-based GoF Template Method while retaining the invariant algorithm skeleton.

Events and domain communication

Application events can decouple an in-process publisher from listeners. Define whether delivery is synchronous or asynchronous, how listener failures affect the publisher, and how transaction boundaries interact. For durable integration events, use a broker/outbox design rather than assuming an in-memory event will survive process failure. The transactional outbox guide shows the durability boundary and why consumers still handle duplicates.

Patterns to use carefully

  • Service Locator through direct ApplicationContext#getBean calls hides dependencies.
  • A repository per table may expose persistence structure rather than domain intent.
  • An interface with exactly one implementation is not automatically wrong, but creating it only because “Spring requires interfaces” is unnecessary.
  • Excessive application events can obscure control flow and failure ownership.
  • A generic base service/controller can erase domain contracts for superficial reuse.

Official references