Decorator pattern
The decorator pattern wraps objects to add behavior at runtime without modifying the original class. Stack decorators like layers, each adding one responsibility while keeping the same interface.
Introduction
Decorator adds responsibilities to an object by wrapping it with another object that implements the same interface. Each wrapper can delegate to the wrapped object and add one focused behavior, so combinations can be assembled at runtime instead of encoded as subclasses.
TL;DR / mental model
Think of decorators as layers around a core object. Every layer is still usable through the component interface, forwards the call inward, and contributes its own behavior on the way through. The nesting order is part of the behavior.
The Problem It Solves
You're building a coffee shop order system. The base coffee costs $2, and customers can add milk, sugar, or whipped cream. Without a pattern, you subclass every combination.
Three toppings produce 8 subclasses. Five produce 32. Every new topping doubles the entire class hierarchy. Testing becomes repetitive because each combination is a separate class with duplicated logic.
Here is what changes when you apply the Decorator pattern.
Instead of one class per combination, you create one class per behavior. Each decorator wraps any Coffee and adds exactly one thing. Three behaviors require three decorator classes, which can be composed into many combinations.
Structure
Coffee is the component interface that both the concrete component (BasicCoffee) and all decorators share. CoffeeDecorator is the abstract decorator: it holds a reference to the wrapped Coffee and forwards calls by default. Each concrete decorator (milk, sugar, whipped cream) overrides methods to add its own behavior before or after delegating to the wrapped object.
The key insight: every decorator IS-A Coffee and HAS-A Coffee. That is what enables stacking.
Participants
- Component: the shared interface exposed to clients.
- Concrete component: the original object that provides the base behavior.
- Decorator: an object that implements the same interface and holds one wrapped component.
- Concrete decorator: adds one responsibility before or after delegating to the wrapped object.
- Client: composes the layers and decides which behaviors apply and in what order.
Implementation
Decorator is useful when behaviors need to be mixed and matched without a subclass explosion. The abstract decorator handles common forwarding, while each concrete decorator focuses on exactly one responsibility.
One decorator, one job
Each decorator should do exactly one thing. If your MilkAndSugarDecorator exists, you've missed the point. Split it into MilkDecorator and SugarDecorator, then compose them.
// Component interface: both real coffees and decorators implement this.
// Clients only depend on this interface, never on concrete classes.
public interface Coffee {
double cost();
String description();
}Why the abstract decorator?
You could skip CoffeeDecorator and have each decorator implement Coffee directly. But then every decorator must forward every method, even the ones it doesn't change. The abstract decorator handles forwarding as the default, so concrete decorators only override the methods they care about.
Implementation notes
- Preserve the component interface at every layer so a decorated object can be passed anywhere the original component is accepted.
- Keep each decorator focused and make composition order visible; short-circuiting, validation, and transformations often have different ordering requirements.
- Decide how equality, identity, metadata, and unwrapping behave before wrappers become part of a public API.
- If forwarding a large interface creates substantial boilerplate, consider a smaller role-specific interface, a composition pipeline, or language/tooling support for delegation.
Decorator ordering and composition
The order you stack decorators changes the behavior. This is powerful but can be surprising.
// Order A: format first, then rate-limit
Notifier orderA = new RateLimitDecorator(new FormattingDecorator(notifier));
// Rate limit check happens first. If allowed, formatting runs, then send.
// Order B: rate-limit first, then format
Notifier orderB = new FormattingDecorator(new RateLimitDecorator(notifier));
// Formatting runs first (wasted work), then rate limit might reject.
The general rule: put short-circuiting decorators (rate limiting, caching, auth checks) on the outside so they reject early. Put transforming decorators (formatting, compression, encryption) closer to the core where they process only accepted requests.
In Spring, you control this with @Order annotations on your decorator beans. In manual composition, it is just the nesting order in the constructor chain.
How It Works
- Client calls
cost()on the outermost decorator. The client has no idea how many layers are wrapped inside. - Each decorator delegates to its wrapped object first.
WhippedCreamDecoratorcallsSugar.cost(), which callsMilk.cost(), which callsBasicCoffee.cost(). - The base component returns its value.
BasicCoffeereturns $2.00. No delegation, this is where the recursion bottoms out. - Each decorator adds its contribution on the way back up. Milk adds $0.50, sugar adds $0.25, whipped cream adds $0.75.
- The client receives the fully composed result. $3.50, with a description of "Basic coffee, milk, sugar, whipped cream."
You can add another topping without modifying BasicCoffee or the existing decorators: write a new class that extends CoffeeDecorator. The nested structure WhippedCream(Sugar(Milk(BasicCoffee))) makes the recursive delegation easy to trace.
Real-World Examples
- Java I/O Streams are the classic decorator example.
BufferedInputStreamwrapsFileInputStream, which wraps a file descriptor.new BufferedInputStream(new FileInputStream("data.txt"))adds buffering without modifying how files are read. Every stream IS-AInputStreamand HAS-AInputStream.
// Three decorators stacked: read file -> buffer -> decompress
InputStream in = new GZIPInputStream( // Decorator 3: decompresses
new BufferedInputStream( // Decorator 2: adds buffering
new FileInputStream("data.gz") // Concrete component: raw file
)
);
// The caller sees one InputStream. Each layer adds one behavior.
Collections.unmodifiableList()wraps anyListwith a decorator that throwsUnsupportedOperationExceptionon write methods. The original list stays mutable internally, but clients with the wrapped reference can only read.- Spring's
HttpServletRequestWrapperis an abstract decorator forHttpServletRequest. Servlet filters use it to modify request attributes (like adding authentication headers) without altering the original request object.
These examples share the same motivation: add a concern to an object without modifying its core implementation, while keeping the caller's interface stable.
When to Use / When NOT to Use
Use when:
- You want to follow the Single Responsibility Principle: each decorator handles one concern
- You need to add responsibilities to objects dynamically without affecting other objects of the same class
- Subclassing would create a combinatorial explosion of classes (3+ independent behaviors that can combine)
- You want behaviors to be stackable and reorderable at runtime
- The component interface is narrow enough that forwarding all methods in the abstract decorator is manageable
Skip when:
- The interface has 20+ methods, making the abstract decorator painful to write and maintain
- You only have one behavior to add and it will never change (just subclass or modify the class)
- Order of decoration must be strictly enforced (decorators don't inherently enforce ordering)
- The decorated object's identity matters (e.g.,
a == bchecks will fail through wrapper layers)
Combinatorial subclass growth is a strong signal for Decorator. For one fixed behavior, a direct method or a focused collaborator may be clearer.
Watch for identity traps
decoratedCoffee.equals(originalCoffee) returns false unless you override equals() in your decorator chain. If your code uses reference equality or relies on instanceof checks, decorators can silently break it.
Trade-offs and alternatives
Decorator keeps combinations flexible, but every layer adds indirection and can make stack order, debugging, identity, and error handling harder to follow. A direct method or focused collaborator is simpler for one stable concern; Strategy is better when one algorithm is selected rather than several responsibilities being stacked; Proxy is better when the main goal is access control or lazy execution.
Common Mistakes and Misconceptions
| # | Common assumption | Why it's incomplete | Better mental model |
|---|---|---|---|
| 1 | "Decorator is the same as inheritance" | Decorator uses composition, so behavior can be assembled per instance. Inheritance fixes the combination in the type hierarchy and can create many subclasses. | "Decorator uses composition to add a layer dynamically; inheritance is a static alternative." |
| 2 | "The decorator replaces the original object" | The original object still exists inside the wrapper and remains responsible for its base behavior. | "The decorator preserves the wrapped component and delegates to it." |
| 3 | "Decorator and Proxy are the same pattern" | They can share the same wrapper structure, but the intent differs. Decorator adds a responsibility; Proxy controls access, timing, or lifecycle. | "Classify the wrapper by why it exists, not only by its UML shape." |
| 4 | "I'd use Decorator to convert between interfaces" | Decorator keeps the component interface. Converting an incompatible interface is the Adapter pattern. | "Same interface plus added behavior suggests Decorator; a changed interface suggests Adapter." |
| 5 | "You always need the abstract decorator class" | A small interface may not need a forwarding base class. The abstract decorator is a convenience for reducing repeated delegation. | "Use the base decorator when it meaningfully reduces boilerplate; otherwise direct implementations are fine." |
Decorator vs Proxy vs Adapter
Use this flowchart when a wrapper is under consideration and you need to identify its intent.
| Dimension | Decorator | Proxy | Adapter |
|---|---|---|---|
| Intent | Add behavior | Control access | Convert interface |
| Interface | Same as wrapped | Same as wrapped | Different from adaptee |
| Wrapping | Multiple layers stacked | Usually single layer | Single layer |
| Client awareness | Client knows it's decorated (chooses toppings) | Client doesn't know (transparency) | Client uses the target interface |
| Typical use | Logging, metrics, formatting, compression | Lazy loading, caching, access control | Legacy integration, third-party library wrapping |
The test: "Am I adding something new?" Then Decorator. "Am I controlling when or whether the real thing runs?" Then Proxy. "Am I translating between two incompatible interfaces?" Then Adapter.
Choosing among wrappers
Start with a simple diagram: one interface, one real object, and one wrapper. Then explain the intent. Decorator and Proxy can have identical structures; what differentiates them is the why, not the how.
Explain It in 30 Seconds
Decorator wraps an object with another object that exposes the same interface. The wrapper delegates to the inner object and adds one responsibility, so several behaviors can be stacked at runtime. Choose Decorator for added behavior, Proxy for controlled access, and Adapter when the interface itself must change.
Explain It in 5 Minutes
Start with the subclass-combination problem: independent behaviors such as buffering, logging, formatting, or toppings produce many subclasses when encoded as inheritance. Define a component interface, a concrete component, and a decorator that both implements the interface and stores one wrapped component. Each concrete decorator adds one concern and delegates the rest. The client composes layers, and the order determines the call and data flow. Discuss the costs: wrappers add indirection, can complicate identity and debugging, and may be awkward around large interfaces or strict ordering rules. If the behavior is fixed and singular, a direct collaborator or subclass may be simpler; if the wrapper controls access rather than adding behavior, use Proxy.
Test Your Understanding
Quick Recap
- Decorator wraps an object with another object that shares the same interface. Each wrapper adds one responsibility. This is composition over inheritance in action.
- The abstract decorator handles method forwarding so concrete decorators only override what they change. Skip the abstract class only when the interface is very small (1-2 methods).
- Decorators are stackable:
Whip(Sugar(Milk(BasicCoffee))). Each layer is independent and reorderable. - Decorator adds behavior; Proxy controls access; Adapter converts interfaces. Same wrapper structure, different intent.
- Order of decoration matters. Put short-circuiting decorators (rate limiting, caching) on the outside; put transforming decorators (formatting, compression) closer to the core.
- Java I/O streams are the textbook example.
BufferedInputStream(FileInputStream)is decorator in production code you use every day. - Explain the recursive structure, trace a method call through the layers, and check that each new decorator adds one responsibility without changing the wrapped component.
Related Patterns
- Proxy pattern - Uses a similar wrapper shape to control access, timing, or lifecycle.
- Adapter pattern - Translates an incompatible interface instead of preserving the same one.
- Composite pattern - Treats a hierarchy of many components uniformly; Decorator wraps one component.
- Strategy pattern - Selects a replaceable algorithm rather than stacking responsibilities around one object.
Related Articles
The strategy pattern extracts a family of algorithms behind an interface so the client can swap behaviors at runtime without touching the context class.
The adapter pattern wraps an incompatible third-party interface so your code can use it through an interface it already expects. Structural bridging without changing either side.
The proxy pattern wraps an object to control access, add caching, or defer creation. Virtual, protection, and caching proxies all share one trick: same interface, different control.