Decorator Pattern

Wrap an object to add behaviour without subclassing. The classic IO-stream example and how to avoid wrapper explosion.

Pattern Intermediate
9 min read
pattern structural decorator wrapper composition

What it is#

The Decorator pattern attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality. A decorator wraps another object that shares its interface, forwards each call, and adds behaviour either before or after the forwarded call.

The pattern has four roles. The Component is the interface both real objects and decorators implement. The ConcreteComponent is the plain object being decorated — a file, a coffee, an HTTP request. The Decorator is an abstract wrapper that holds a Component reference and forwards every method to it. ConcreteDecorators extend the Decorator and add the new behaviour around the forwarded call.

The key insight is that decorators stack. Because a decorator is a Component, you can wrap a decorator in another decorator, and the outer one will not know — or care — whether the inner one is concrete or another decorator. The chain looks linear from outside but reads as outer.method calling inner.method recursively.

Class structure#

┌──────────────────────┐
│ Component │
├──────────────────────┤
│ operation() │
└─────────▲────────────┘
┌─────────────┴────────────┐
│ │
┌────────┴─────────┐ ┌────────┴───────────┐
│ ConcreteComponent│ │ Decorator │◇──┐
├──────────────────┤ ├────────────────────┤ │ wrapped
│ operation() │ │ operation() │ │
└──────────────────┘ │ { wrapped.op() } │◄──┘
└─────────▲──────────┘
┌────────────────┴─────────────────┐
│ │
┌────────┴────────────┐ ┌───────────┴─────────┐
│ ConcreteDecoratorA │ │ ConcreteDecoratorB │
├─────────────────────┤ ├─────────────────────┤
│ operation() │ │ operation() │
│ { super.op(); │ │ { super.op(); │
│ addedA(); } │ │ addedB(); } │
└─────────────────────┘ └─────────────────────┘

The self-pointing diamond on Decorator is the recursion: a Decorator holds a Component, and a Decorator is a Component, so decorators can nest indefinitely.

When to use it#

Reach for Decorator when all of these hold:

  • You want to add behaviour to individual objects, not the whole class.
  • The behaviour can be layered — buffering, then compression, then encryption — and the order of layering is meaningful.
  • The combinations of features are combinatorial: subclassing every combination (BufferedGzippedEncryptedFile, BufferedEncryptedGzippedFile, …) would explode.
  • You can decorate at runtime, not just at compile time.

Typical scenarios:

  • Streams and pipelines. Layering buffering, compression, encryption, checksumming around a raw byte source.
  • Cross-cutting concerns. Logging, timing, caching, retry, and authentication added around a domain service interface.
  • UI widgets. Adding borders, scrollbars, drop shadows to a base widget without baking them into the widget class.
  • Coffee shops in textbooks. The canonical interview example — a Beverage decorated with Milk, Sugar, WhippedCream at runtime, each adding to cost() and description().

When not to use it:

  • When you need to add state that all wrappers must agree on — decorators are independent layers; shared state is awkward.
  • When the wrappers would change the interface rather than add to it. That is Adapter.
  • When there is a fixed, small set of variants. Subclassing or Strategy is simpler.
  • When the chain becomes deep and order-sensitive — past three or four layers, readability collapses. See “wrapper explosion” below.

How it works#

A coffee-shop example, the standard interview prompt. Each decorator forwards cost and description to the wrapped beverage and adds its own contribution.

// --- Component: the interface both base and decorators implement ---
public interface Beverage {
double cost();
String description();
}
// --- ConcreteComponents: the unadorned base drinks ---
public final class Espresso implements Beverage {
@Override public double cost() { return 1.99; }
@Override public String description() { return "Espresso"; }
}
public final class HouseBlend implements Beverage {
@Override public double cost() { return 0.89; }
@Override public String description() { return "House Blend"; }
}
// --- Decorator: the abstract wrapper that forwards calls ---
public abstract class BeverageDecorator implements Beverage {
protected final Beverage wrapped;
protected BeverageDecorator(Beverage wrapped) { this.wrapped = wrapped; }
}
// --- ConcreteDecorators: each adds one responsibility ---
public final class Milk extends BeverageDecorator {
public Milk(Beverage wrapped) { super(wrapped); }
@Override public double cost() { return wrapped.cost() + 0.30; }
@Override public String description() { return wrapped.description() + ", milk"; }
}
public final class Mocha extends BeverageDecorator {
public Mocha(Beverage wrapped) { super(wrapped); }
@Override public double cost() { return wrapped.cost() + 0.45; }
@Override public String description() { return wrapped.description() + ", mocha"; }
}
public final class Whip extends BeverageDecorator {
public Whip(Beverage wrapped) { super(wrapped); }
@Override public double cost() { return wrapped.cost() + 0.20; }
@Override public String description() { return wrapped.description() + ", whip"; }
}
// Wiring:
// Beverage drink = new Whip(new Mocha(new Milk(new Espresso())));
// System.out.println(drink.description()); // "Espresso, milk, mocha, whip"
// System.out.println(drink.cost()); // 2.94

Five details worth pointing out:

  • The wrapped reference is final. Decorators are immutable once constructed; if you need a different inner drink, build a new decorator. This is what makes the pattern thread-safe.
  • The decorator extends an abstract class, not the component interface directly. The abstract BeverageDecorator exists only to hold the wrapped field; subclasses skip the boilerplate.
  • Every method must forward. A decorator that overrides cost but forgets description silently drops the wrapped value. The compiler enforces this only if you make the wrapper class explicit and remember to delegate.
  • Order matters. new Whip(new Milk(...)) and new Milk(new Whip(...)) produce the same total but different descriptions. For non-commutative operations (encrypt-then-compress vs compress-then-encrypt), the order is the bug.
  • No reflection, no proxies. The pattern is plain composition — every type is visible at compile time.

Variants#

VariantMechanismWhen it fits
Classic GoFAbstract Decorator + concrete decorators, manually nested.Small numbers of layers; clarity matters more than ergonomics.
Fluent / builder-styleA builder collects decorator flags and assembles the chain in one expression.Many layers, frequent reordering, or callers should not see the nesting.
Dynamic-proxy decoratorjava.lang.reflect.Proxy generates the wrapper at runtime; InvocationHandler decides what to add.Wide interfaces where hand-writing forwarders would be 200 lines of boilerplate (logging, timing, caching aspects).
Function-composition decoratorThe “component” is a Function<I, O>; decorators are Function adapters composed with andThen.Stateless transformations on a pipeline.

The fluent variant is the usual answer to wrapper explosion — when readers cannot tell what is happening because the construction expression is five levels deep:

// Painful: which decorator is on the outside?
new Whip(new Mocha(new Milk(new Espresso())));
// Easier to read: builder-style
Beverage drink = BeverageBuilder.of(new Espresso())
.with(Milk::new)
.with(Mocha::new)
.with(Whip::new)
.build();

The builder still produces the same nested-decorator chain; it just names the layers in order top to bottom.

Example systems#

java.io is the textbook decorator family. A raw InputStream produces bytes from somewhere — a file, a socket, an array. Every other stream class is a decorator that wraps an InputStream and adds one responsibility:

// Read a gzipped file with buffered IO and a checksum:
InputStream in = new CheckedInputStream(
new GZIPInputStream(
new BufferedInputStream(
new FileInputStream("data.gz"))),
new CRC32());

Each layer is a FilterInputStream subclass (the JDK’s name for BeverageDecorator). Reading a byte from the outer stream walks down through the chain — checksum updates, decompresses one block, fills the buffer, reads from the file. The same pattern appears in Reader / Writer (character side), OutputStream (write side), and java.util.zip (compression layer).

Beyond the JDK:

  • Servlet filters. HttpServletRequest is decorated by each filter in the chain — adding authentication context, parsing multipart bodies, gzipping responses on the way out.
  • HTTP client interceptors. Every modern HTTP client (OkHttpClient, Spring RestTemplate, Apache HttpClient) exposes an interceptor list that is functionally a decorator chain around the underlying transport.
  • Spring Bean post-processors and AOP advice. When @Transactional or @Cacheable is applied to a bean, Spring wraps the bean in a dynamic-proxy decorator that opens the transaction or checks the cache before forwarding.
  • GUI toolkits. Swing’s JScrollPane is a decorator around any JComponent; nested scroll panes around panels around buttons compose without each layer needing to know the others.

Trade-offs#

What you gain:

  • Open-closed extension. New behaviours arrive as new decorator classes; existing components and decorators stay untouched.
  • Combinatorial reuse with linear class count. Five decorators give you 2^5 = 32 possible combinations from six classes (one component + five decorators), not 32 subclasses.
  • Runtime composition. Which decorators to apply can be a configuration choice or even a user choice — subclassing cannot do this.
  • Single responsibility per layer. Each decorator owns exactly one concern, which makes them small and testable.

What you pay:

  • Wrapper explosion. Deep chains are hard to read, hard to debug, and easy to misorder. Use a builder for anything past three layers.
  • Identity is awkward. new Milk(espresso).equals(espresso) is false even though they “are” the same drink. If callers care about identity (caching, weak refs), decorators surprise them.
  • Boilerplate. Every method on the Component interface must be forwarded by every decorator. Wide interfaces make hand-written decorators painful; dynamic proxies are the escape hatch.
  • Stack traces get long. A failure inside the innermost component reports through every wrapper. For dynamic-proxy variants, the frames are synthetic and unreadable until you have seen them before.
  • Type-specific operations break. If a caller needs ((BufferedInputStream) in).mark(), it has to unwrap the chain, which the decorator pattern explicitly resists.
  • Adapter Pattern — same wrap-and-delegate shape, different intent. Adapter changes the interface and adds no behaviour; Decorator keeps the interface and adds behaviour.
  • Composite Pattern — Decorator is structurally a degenerate Composite with exactly one child. Both rely on the wrapper being a subtype of what it wraps.
  • Strategy Pattern — Strategy changes an object’s behaviour by swapping a collaborator; Decorator changes it by wrapping it. Strategy is one-of; Decorator is many-of-stacked.
  • Open Closed Principle (OCP) — adding a new feature as a new decorator class is the textbook OCP win.
  • OOP Fundamentals — The Four Pillars — Decorator is the canonical example of “favour composition over inheritance”; the inheritance-heavy alternative is the wrapper explosion this pattern was invented to avoid.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.