Home/Learn/Java A–Z/Decorator Pattern

Decorator Pattern

Intermediate
Design Patterns

Decorator wraps an object to add behaviour dynamically, composing features at runtime without modifying the original class.

Overview

The Decorator pattern attaches additional responsibilities to an object at runtime by wrapping it. Decorators implement the same interface as the wrapped object, so they can be stacked in any combination. Java's I/O streams (BufferedInputStream, GZIPOutputStream, DataInputStream) are the classic Decorator example in the JDK. Decorators are a flexible alternative to subclassing for extending behaviour.

Classic Decorator

Define a component interface. The ConcreteComponent provides the base implementation. Decorators extend an abstract Decorator class that holds a reference to a Component and delegates to it, adding behaviour before or after the delegation.

Decorators can be stacked — each wraps the one beneath it, adding a layer of behaviour.

CoffeeDecorator.java
// Component interface
public interface Coffee {
    String getDescription();
    double getCost();
}

// Concrete component
public class SimpleCoffee implements Coffee {
    @Override public String getDescription() { return "Coffee"; }
    @Override public double getCost()        { return 1.00; }
}

// Abstract decorator
public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee wrapped;
    public CoffeeDecorator(Coffee coffee) { this.wrapped = coffee; }
    @Override public String getDescription() { return wrapped.getDescription(); }
    @Override public double getCost()        { return wrapped.getCost(); }
}

// Concrete decorators
public class Milk extends CoffeeDecorator {
    public Milk(Coffee c) { super(c); }
    @Override public String getDescription() { return wrapped.getDescription() + ", Milk"; }
    @Override public double getCost()        { return wrapped.getCost() + 0.25; }
}

public class Vanilla extends CoffeeDecorator {
    public Vanilla(Coffee c) { super(c); }
    @Override public String getDescription() { return wrapped.getDescription() + ", Vanilla"; }
    @Override public double getCost()        { return wrapped.getCost() + 0.50; }
}

// Stack decorators at runtime
Coffee order = new Vanilla(new Milk(new Milk(new SimpleCoffee())));
System.out.println(order.getDescription()); // Coffee, Milk, Milk, Vanilla
System.out.println(order.getCost());        // 2.00

Java I/O Streams as Decorators

Java's InputStream hierarchy is the most famous Decorator in the JDK. FileInputStream provides raw bytes; BufferedInputStream adds buffering; GZIPInputStream adds decompression; DataInputStream adds typed reads — all composable.

This design allows any combination of features without a class explosion from subclassing.

IODecorator.java
// Layered I/O decorators
try (InputStream raw  = new FileInputStream("data.gz");
     InputStream zip  = new GZIPInputStream(raw);
     InputStream buf  = new BufferedInputStream(zip);
     DataInputStream data = new DataInputStream(buf)) {

    int count = data.readInt();
    for (int i = 0; i < count; i++) {
        System.out.println(data.readUTF());
    }
}

// Writer decorators — same principle
try (Writer fw = new FileWriter("out.txt");
     Writer bw = new BufferedWriter(fw);
     PrintWriter pw = new PrintWriter(bw)) {
    pw.println("Hello, Decorator!");
}

Decorator with Functional Interfaces

With functional interfaces, decorators can be implemented as higher-order functions — functions that wrap other functions. This is the functional programming equivalent of the Decorator pattern.

FunctionalDecorator.java
import java.util.function.*;

// Decorator as function wrapper
Function<String, String> trim       = String::trim;
Function<String, String> uppercase  = String::toUpperCase;
Function<String, String> addBrackets = s -> "[" + s + "]";

// Compose decorators with andThen / compose
Function<String, String> pipeline =
    trim.andThen(uppercase).andThen(addBrackets);

System.out.println(pipeline.apply("  hello  "));
// [HELLO]

// Logging decorator for any Function
static <T, R> Function<T, R> withLogging(
        Function<T, R> fn, String name) {
    return input -> {
        System.out.println("Calling " + name + " with: " + input);
        R result = fn.apply(input);
        System.out.println("Result: " + result);
        return result;
    };
}

Function<Integer, Integer> doubler = withLogging(n -> n * 2, "doubler");
doubler.apply(5); // logs call and result

Key Points to Remember

  • Decorator wraps a component implementing the same interface, adding behaviour via delegation.
  • Decorators can be stacked in any combination — this is more flexible than inheritance.
  • Java I/O streams (BufferedInputStream, GZIPInputStream, DataInputStream) are the classic JDK example.
  • Functional Decorator = higher-order function that wraps another function.
  • The key difference from inheritance: Decorator adds behaviour at runtime, not compile time.

Practice Decorator Pattern in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

How does Java's I/O stream hierarchy demonstrate the Decorator pattern?

MediumOracle
2

What is the difference between Decorator and inheritance for extending behaviour?

MediumGoogle
3

What is the difference between Decorator and Proxy patterns?

HardAmazon
4

How would you implement a caching decorator for a database repository?

HardNetflix
5

Can you stack multiple Decorators? What are the implications?

MediumMicrosoft

Ask Aria about Decorator Pattern

Your personal AI tutor — ask anything about this concept