About Us Contact Us Write for Us Advertise
Home > System Design > Low-Level Design > Decorator Pattern in Java (with Example)
System Design › Low-Level Design

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.

Shiv Pandey
Shiv Pandey
Oct 20, 2026 | 31 views
Decorator Pattern in Java (with Example)

🟠 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.

In one line: Decorator wraps an object in another object of the same type that adds behaviour, so you can stack features dynamically without changing the original class or exploding into subclasses.

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"
SimpleCoffee $2 Milk +0.5 Sugar +0.2 = $2.70 each wraps the previous, adding behaviour

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

← Adapter pattern · Facade pattern →

Related Articles

Chain of Responsibility Pattern in Java (Example)
System Design › Low-Level Design

Chain of Responsibility Pattern in Java (Example)

Template Method Pattern in Java (with Example)
System Design › Low-Level Design

Template Method Pattern in Java (with Example)

Command Pattern in Java (with Undo Example)
System Design › Low-Level Design

Command Pattern in Java (with Undo Example)

State Pattern in Java (with Example)
System Design › Low-Level Design

State Pattern in Java (with Example)