Decorator Pattern in Java (with Example)
The Decorator pattern explained: add behaviour to an object at runtime by wrapping it, stacking features like milk and sugar on coffee — instead of a subclass explosion. Java I/O streams, code, UML, when to use.
🟠Design Patterns · Structural · Senior
The Decorator pattern lets you add new behaviour to an object at runtime by wrapping it — like adding milk, then sugar, then whipped cream to a coffee, each layer adding to the price and description. It's the elegant alternative to a subclass explosion, and it's the pattern behind Java's I/O streams. Once it clicks, you'll see wrapping everywhere.
The problem: subclass explosion
Say a Coffee costs $2. You want options: milk, sugar, whipped cream, caramel. With inheritance you'd need CoffeeWithMilk, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndCream… every combination is a new class — dozens of them. Adding one option doubles the classes. That's the subclass explosion Decorator solves.
The solution: wrap and add
🟠Make each add-on a decorator that wraps a coffee and implements the same Coffee interface, adding to the result:
interface Coffee { double cost(); String desc(); }
class SimpleCoffee implements Coffee {
public double cost(){ return 2.0; } public String desc(){ return "Coffee"; }
}
abstract class Add implements Coffee { // base decorator
protected final Coffee inner;
Add(Coffee c){ this.inner = c; }
}
class Milk extends Add {
Milk(Coffee c){ super(c); }
public double cost(){ return inner.cost() + 0.5; }
public String desc(){ return inner.desc() + " + Milk"; }
}
class Sugar extends Add {
Sugar(Coffee c){ super(c); }
public double cost(){ return inner.cost() + 0.2; }
public String desc(){ return inner.desc() + " + Sugar"; }
}
// stack them at runtime:
Coffee c = new Sugar(new Milk(new SimpleCoffee()));
c.cost(); // 2.7 c.desc(); // "Coffee + Milk + Sugar"
Because every decorator is a Coffee, you can stack them in any order and any number. Two options, ten options — no new combination classes. You add features by composition at runtime, not inheritance at compile time.
You've used it: Java I/O
🟠The textbook real example is Java streams: new BufferedReader(new InputStreamReader(new FileInputStream(file))). Each wraps the previous, adding capability — file bytes → characters → buffering. That's Decorator, layer by layer. Pointing to Java I/O instantly shows you understand it in the wild.
Decorator vs inheritance
🟠This pattern is the poster child for "favour composition over inheritance." Inheritance fixes behaviour at compile time and multiplies classes; Decorator adds behaviour at runtime by wrapping, so combinations are free and flexible. It also honours Open/Closed — new features are new decorator classes, no edits to existing ones.
When to use it
Use Decorator when you need to add responsibilities to objects dynamically and in combinations, and subclassing every combination would explode. Common in UI (scrollable, bordered, shadowed widgets), I/O streams, and adding cross-cutting features like compression or encryption to a data source. If you only ever need one fixed variant, plain subclassing is simpler.
The interview-ready summary
"Decorator adds behaviour by wrapping an object in another of the same interface, so I can stack features at runtime — milk then sugar on a coffee — instead of creating a subclass per combination (which explodes). Each decorator delegates to the wrapped object and adds its bit. It favours composition over inheritance and satisfies Open/Closed. The classic real example is Java I/O streams (BufferedReader wrapping InputStreamReader wrapping a file stream)." That nails the intent, the anti-explosion motivation, and a real example."
What to read next
- Facade Pattern — simplifying a complex subsystem
- Proxy Pattern — another wrapping pattern, for access control
- All Design Patterns (hub)
- ← The complete System Design guide (hub)