Decorator Design Pattern in Java: Complete Guide with Examples (2026)

Decorator Design Pattern in Java: Complete Guide with Examples (2026)

Decorator Design Pattern in Java: Complete Guide with Examples

Learn the Decorator design pattern in Java with simple explanations and practical examples. This guide covers how to add new behavior to objects dynamically using wrappers, with before-and-after code for beginners and experts.

I've been writing Java for years, and Decorator is the pattern I reach for whenever I see an inheritance explosion. Not because inheritance is bad—because the moment you create MilkCoffee, SugarCoffee, and MilkSugarCoffee, you know you'll regret it. Here's how Decorator saved me from that trap.

Why the Decorator Pattern Still Matters in Java

You've built a class. It works. Then requirements arrive: add logging, add caching, add encryption, add compression. You start creating subclasses for every combination. Before you know it, you have LoggingCachingEncryptedFileWriter and the class name alone fills your screen.

The Decorator pattern solves this by letting you wrap an object with another object that has the same interface. The wrapper adds behavior before or after delegating to the original. You can stack wrappers as many times as you need, in any order, at runtime.

Whether you're building file streams, UI components, or HTTP clients, Decorator keeps your code flexible without the inheritance nightmare.

What is the Decorator Design Pattern?

"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."

In plain words: Decorator wraps an object with another object that has the same interface. The wrapper holds a reference to the original and adds behavior around the call. Since both share the interface, clients don't know they're talking to a wrapper.

The Four Players

Role Responsibility Example
Component Common interface for original and wrappers Coffee
Concrete Component The original object being wrapped SimpleCoffee
Decorator Abstract class that holds a reference to Component CoffeeDecorator
Concrete Decorator Adds specific behavior MilkDecorator, SugarDecorator
Decorator Design Pattern Structure Diagram Component interface extended by ConcreteComponent (SimpleCoffee) and abstract Decorator (CoffeeDecorator). Decorator holds a reference to Component and is extended by ConcreteDecorators (MilkDecorator, SugarDecorator). DECORATOR PATTERN STRUCTURE «interface» Coffee + cost() / description() SimpleCoffee (Concrete Component) + cost() → 50 CoffeeDecorator (Abstract Decorator) holds a Coffee reference MilkDecorator + cost() → +20 SugarDecorator + cost() → +10 wraps Component Concrete Component / Concrete Decorator Abstract Decorator

Decorator pattern structure: MilkDecorator wraps a Coffee, adds cost, and delegates to the original.

How to read this diagram (for freshers): The blue box at the top is the Component—the common interface. On the left, SimpleCoffee is the original object (Concrete Component). On the right, CoffeeDecorator is the abstract wrapper that holds a reference to a Coffee (that's the red dashed arrow). The two yellow boxes at the bottom are concrete wrappers. When you call cost() on a MilkDecorator, it calls the original coffee's cost() first, then adds 20. You can wrap a MilkDecorator inside a SugarDecorator inside another MilkDecorator—stacking behavior at runtime.

Real-World Example for Freshers

Before we touch code, let's look at the Decorator pattern you already use every day.

Example 1: Ordering Coffee at a Cafe

You walk into a coffee shop. You order a basic coffee. Then you add milk. Then you add sugar. Then you add whipped cream.

The barista doesn't create a new "MilkSugarWhippedCreamCoffee" object. The barista takes your basic coffee and wraps it with each addition, one by one. Each topping adds to the price. The final cup is still a coffee—but it's been decorated.

That's the Decorator pattern. The base object stays the same. The wrappers add behavior (and cost) on top.

Example 2: Java I/O Streams (You Already Use This)

Every time you write this line of Java, you're using the Decorator pattern:

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));

Let's break it down:

  • FileInputStream reads raw bytes from a file (the original object)
  • InputStreamReader wraps it and converts bytes to characters (a decorator)
  • BufferedReader wraps that and adds buffering plus readLine() (another decorator)

All three share the Reader interface. You can stack them in any order. You can add a LineNumberReader on top if you want line numbers. That's Decorator in the standard library.

Example 3: Pizza Toppings

A pizza base is your Concrete Component. Cheese, mushrooms, olives, and pepperoni are decorators. You don't create PizzaWithCheeseAndMushroomsAndOlives. You start with a base pizza and wrap it with each topping. Each topping adds to the price and modifies the description.

Quick Summary for Freshers

Real-World Situation Original Object Decorators
Coffee shop SimpleCoffee Milk, Sugar, Whip
Pizza Base Pizza Cheese, Olives, Mushrooms
Java I/O FileInputStream InputStreamReader, BufferedReader
HTTP client BasicClient LoggingClient, RetryClient, AuthClient

Once you see it in your coffee order, you'll see it everywhere. That's why Decorator is one of the most practical design patterns in everyday software.

Violation: The Class Explosion Nightmare

Imagine you're building the coffee shop app. You have a Coffee class. You want to support milk, sugar, and whipped cream. With inheritance, you create:

class Coffee { }
class MilkCoffee extends Coffee { }
class SugarCoffee extends Coffee { }
class WhipCoffee extends Coffee { }
class MilkSugarCoffee extends Coffee { }
class MilkWhipCoffee extends Coffee { }
class SugarWhipCoffee extends Coffee { }
class MilkSugarWhipCoffee extends Coffee { }

// Add caramel? Eight more classes.
// Add vanilla? Sixteen more classes.

The Problem: Every new topping doubles the class count. This is the classic "class explosion" that inheritance causes when you have combinations of features. Decorator eliminates this entirely.

Solution: Decorator with Coffee and Toppings

Let's build a coffee ordering system where toppings are wrappers, not subclasses. The client calls cost() once, and the wrappers add their prices automatically.

Step 1: The Component Interface

public interface Coffee {
    double cost();
    String description();
}

Step 2: The Concrete Component (Original Coffee)

public class SimpleCoffee implements Coffee {
    @Override
    public double cost() {
        return 50.0;
    }

    @Override
    public String description() {
        return "Simple Coffee";
    }
}

Step 3: The Abstract Decorator

public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }

    @Override
    public String description() {
        return decoratedCoffee.description();
    }
}

Step 4: The Concrete Decorators

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 20.0;
    }

    @Override
    public String description() {
        return super.description() + ", Milk";
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 10.0;
    }

    @Override
    public String description() {
        return super.description() + ", Sugar";
    }
}

Step 5: Using the Decorator

public class Demo {
    public static void main(String[] args) {
        // Plain coffee
        Coffee plain = new SimpleCoffee();
        System.out.println(plain.description() + " = Rs." + plain.cost());

        // Coffee with milk
        Coffee milk = new MilkDecorator(new SimpleCoffee());
        System.out.println(milk.description() + " = Rs." + milk.cost());

        // Coffee with milk and sugar
        Coffee milkSugar = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
        System.out.println(milkSugar.description() + " = Rs." + milkSugar.cost());

        // Coffee with milk, sugar, and extra milk
        Coffee doubleMilk = new MilkDecorator(
            new SugarDecorator(
                new MilkDecorator(new SimpleCoffee())
            )
        );
        System.out.println(doubleMilk.description() + " = Rs." + doubleMilk.cost());
    }
}

Output:

Simple Coffee = Rs.50.0
Simple Coffee, Milk = Rs.70.0
Simple Coffee, Milk, Sugar = Rs.80.0
Simple Coffee, Milk, Sugar, Milk = Rs.100.0

What just happened? The client called cost() on the outermost wrapper. That wrapper called its inner coffee's cost(), added its own price, and returned. The chain unwound automatically. The client never wrote a single if or instanceof.

For Beginners: The Gift Wrap Analogy

Think of buying a gift. You start with a book (the original object). You wrap it in bubble wrap (a decorator). Then you wrap that in gift paper (another decorator). Then you put it in a gift bag (a third decorator). The final package is still "a book"—but it's been decorated. Each layer adds something. You can add layers in any order. You can add as many as you want.

Decorator is the gift wrap. Inheritance would be creating a "BubbleWrappedBook" class, a "GiftPaperBook" class, and a "BubbleWrappedGiftPaperBook" class.

For Experts: Decorator vs. Composite (Know the Difference)

This is a common interview trap.

Aspect Decorator Composite
Structure Chain (one wrapped child) Tree (many children)
Intent Augment behavior Aggregate part-whole
Children Exactly 1 0 to many
Focus Behavior Structure

Rule of thumb: Decorator is about one thing wearing many hats. Composite is about many things acting as one.

Putting It All Together: Java I/O Example

The most famous real-world Decorator in Java is java.io. Every stream wrapper is a decorator. Here's the classic chain:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("data.txt")
    )
);

The Decorator Structure

Role Java Class
Component Reader
Concrete Component FileInputStream (wrapped by InputStreamReader)
Decorator FilterReader
Concrete Decorators BufferedReader, LineNumberReader

BufferedReader adds buffering and readLine() to whatever Reader it wraps. You can wrap it around a file reader, a socket reader, or a string reader. The decorator doesn't care about the underlying source.

Personal note: I once had to add logging and retry logic to an HTTP client. My first instinct was to create subclasses. Then I realized I'd need LoggingClient, RetryClient, and LoggingRetryClient—and if I added auth, that was eight classes. I refactored to Decorator. The base client stayed untouched. Adding a feature meant writing one small wrapper class.

Frequently Asked Questions

What is the Decorator design pattern in Java?

Decorator is a structural pattern that lets you attach new behaviors to objects dynamically by placing them inside wrapper objects. The wrapper has the same interface as the original object, so clients see no difference.

When should I use the Decorator pattern?

Use Decorator when you need to add responsibilities to individual objects without affecting others, when subclassing would cause a class explosion, or when behaviors can be combined in many ways at runtime.

How is Decorator different from Inheritance?

Inheritance adds behavior at compile time and affects all instances of a class. Decorator adds behavior at runtime to specific objects. Inheritance creates an is-a relationship; Decorator creates a has-a (wraps) relationship.

How is Decorator different from Composite?

Composite aggregates many children into a tree and delegates to all of them. Decorator wraps exactly one object and adds behavior around it. Composite is about structure; Decorator is about augmenting behavior.

Where is Decorator used in real Java code?

Java I/O is the classic example. BufferedReader wraps InputStreamReader which wraps FileInputStream. All share the Reader interface, so you can stack them. Collections.synchronizedList() and Collections.unmodifiableList() are also decorators.

Your Decorator Checklist

Before creating another subclass, ask:

  • Am I multiplying classes? If you have XWithA, XWithB, XWithAAndB—stop and consider Decorator.
  • Should behavior be added at runtime? If the combination of features isn't known until the user configures it, Decorator is right.
  • Can I stack features? If adding feature A and feature B should work together, Decorator supports that naturally.
  • Am I wrapping an existing object? If you have an object that needs extra behavior without changing its class, Decorator is the answer.

Start small. Refactor one exploding hierarchy. The clarity compounds.

Conclusion

The Decorator pattern isn't about complexity—it's about flexibility. When you need to add behavior to individual objects without touching their class, Decorator gives you a wrapper that stacks cleanly. It trades a small upfront design decision for long-term freedom from class explosion.

I'll be honest: I don't use Decorator in every project. Simple objects with fixed behavior don't need it. But for anything that touches streams, HTTP clients, UI components, or configurable features, it has saved me from writing the same subclass combinations over and over. Start with the problem, not the pattern. If you find yourself writing XWithAAndB, Decorator is probably the answer.

Related topics: SOLID Principles in Java, Composite Design Pattern, Bridge Design Pattern, Java I/O Streams, Structural Design Patterns

No comments: