Skip to content
Home Java Decorator Pattern in Java

Decorator Pattern in Java

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Advanced Java → Topic 17 of 28
Decorator pattern in Java — how it works, a step-by-step example with I/O streams, when to use decorator vs inheritance, and how Java's own BufferedReader uses it.
⚙️ Intermediate — basic Java knowledge assumed
In this tutorial, you'll learn
Decorator pattern in Java — how it works, a step-by-step example with I/O streams, when to use decorator vs inheritance, and how Java's own BufferedReader uses it.
  • Decorator adds behaviour at runtime by wrapping an object that implements the same interface.
  • The key structure: decorator holds a reference to the wrapped object and delegates to it.
  • Decorators compose freely — wrap N decorators around one component for N behaviours.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

The Decorator pattern adds behaviour to an object at runtime without modifying its class. A decorator wraps another object of the same interface, calls the wrapped object's methods, and adds extra logic before or after. Java's I/O library (BufferedReader wrapping FileReader, GZIPInputStream wrapping FileInputStream) is the canonical real-world example.

Building a Decorator — Step by Step

Example · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
package io.thecodeforge.java.patterns;

// Step 1: Define the component interface
interface TextProcessor {
    String process(String text);
}

// Step 2: Concrete component
class PlainTextProcessor implements TextProcessor {
    @Override
    public String process(String text) {
        return text;
    }
}

// Step 3: Abstract decorator — holds a reference to the wrapped component
abstract class TextProcessorDecorator implements TextProcessor {
    protected final TextProcessor wrapped;
    TextProcessorDecorator(TextProcessor wrapped) { this.wrapped = wrapped; }
}

// Step 4: Concrete decorators
class UpperCaseDecorator extends TextProcessorDecorator {
    UpperCaseDecorator(TextProcessor wrapped) { super(wrapped); }
    @Override public String process(String text) {
        return wrapped.process(text).toUpperCase(); // add before/after
    }
}

class TrimDecorator extends TextProcessorDecorator {
    TrimDecorator(TextProcessor wrapped) { super(wrapped); }
    @Override public String process(String text) {
        return wrapped.process(text.trim());  // modify input
    }
}

class PrefixDecorator extends TextProcessorDecorator {
    private final String prefix;
    PrefixDecorator(TextProcessor wrapped, String prefix) {
        super(wrapped);
        this.prefix = prefix;
    }
    @Override public String process(String text) {
        return prefix + wrapped.process(text);
    }
}

public class DecoratorDemo {
    public static void main(String[] args) {
        // Compose freely at runtime
        TextProcessor simple = new PlainTextProcessor();
        TextProcessor trimmed = new TrimDecorator(simple);
        TextProcessor upper   = new UpperCaseDecorator(trimmed);
        TextProcessor tagged  = new PrefixDecorator(upper, "[RESULT] ");

        System.out.println(tagged.process("  hello world  "));
        // [RESULT] HELLO WORLD
    }
}
▶ Output
[RESULT] HELLO WORLD

Java I/O — The Real-World Decorator Example

Example · JAVA
12345678910111213141516171819202122232425262728293031
package io.thecodeforge.java.patterns;

import java.io.*;
import java.util.zip.GZIPInputStream;

public class IODecorators {
    public static void main(String[] args) throws Exception {
        // Each wrapper adds behaviour without modifying the inner class:
        // FileReader: reads characters from a file
        // BufferedReader: adds buffering (reads in chunks, not char by char)
        // — both implement Reader

        try (BufferedReader reader = new BufferedReader(
                new FileReader("data.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }

        // Stack more decorators for more behaviour:
        // FileInputStream → GZIPInputStream → InputStreamReader → BufferedReader
        try (BufferedReader gzipReader = new BufferedReader(
                new InputStreamReader(
                    new GZIPInputStream(
                        new FileInputStream("data.gz"))))) {
            System.out.println(gzipReader.readLine());
        }
        // No class was modified — behaviour added by wrapping
    }
}
▶ Output
// Reads from file — output depends on file content

🎯 Key Takeaways

  • Decorator adds behaviour at runtime by wrapping an object that implements the same interface.
  • The key structure: decorator holds a reference to the wrapped object and delegates to it.
  • Decorators compose freely — wrap N decorators around one component for N behaviours.
  • Java's I/O library (BufferedReader, GZIPInputStream) is the most-used Decorator in the JDK.
  • Prefer Decorator over inheritance when you need combinations of optional behaviours.

Interview Questions on This Topic

  • QExplain the Decorator pattern and give a real Java example.
  • QWhat problem does the Decorator pattern solve that inheritance cannot?
  • QHow is Java's BufferedReader an example of the Decorator pattern?

Frequently Asked Questions

What is the difference between Decorator and Inheritance?

Inheritance adds behaviour at compile time — you choose the subclass when writing code. Decorator adds behaviour at runtime — you choose the wrappers when creating objects. Inheritance creates an explosion of subclasses for combinations; Decorator creates a few wrappers that combine freely.

What is the difference between Decorator and Proxy?

Both wrap an object of the same interface. Decorator adds new behaviour — it extends what the object does. Proxy controls access — it may restrict, delay, cache, or log access to the underlying object without fundamentally changing its behaviour. The distinction is intent.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousStrategy Pattern in JavaNext →Dependency Injection in Java
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged