In software architecture, the Decorator Design Pattern is a structural pattern used to dynamically extend the functionality of an object at runtime without altering its structure. Instead of relying on subclassing to multiply behaviors, the Decorator pattern wraps an object inside another class, creating a layered execution pipeline.

Java's java.io package is one of the most famous real-world implementations of this design pattern. Classes like BufferedReader and FilterReader wrap around raw inputs (like FileReader or FileInputStream) to add capabilities like buffering and character conversion. In this guide, we will leverage this pattern to build a custom BufferedReader subclass that intercepts read lines and automatically capitalizes them on the fly.

Real-World Analogy: The Faucet Filter Attachment

To visualize the Decorator pattern, think about your kitchen sink's water faucet. The faucet is connected to a basic water pipe (representing the standard BufferedReader) that flows raw tap water out.

If you want pure, carbon-filtered water, you do not rip out the entire plumbing system. Instead, you buy a filtration attachment (representing our custom CapReader decorator) and screw it onto the end of the faucet.

As the tap water flows out of the main pipe, it passes through the filter, which purifies it or adds flavor before it reaches your glass. The faucet remains unchanged, and the water pipe remains unchanged. The filter simply decorates the water stream on the fly as it exits the system.

Creating the Custom Decorator Class

To implement our custom text filter in Java, we extend the BufferedReader class. This choice allows our decorator to inherit all standard buffering capabilities. We override the readLine() method. In our implementation, we call super.readLine() to fetch the raw text from the underlying file reader. If the fetched line is not null, we intercept the string, convert it to uppercase using line.toUpperCase(), and return the modified string. This intercepts and decorates the data stream transparently.

Here is the Java class definition for our custom CapReader decorator:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
 
class CapReader extends BufferedReader {
    public CapReader(Reader arg0) {
        super(arg0); // Pass reader to super constructor
    }
 
    @Override
    public String readLine() throws IOException {
        String line = super.readLine(); // Fetch raw line
        if (line != null) {
            return line.toUpperCase(); // Decorate line by converting to uppercase
        }
        return null;
    }
}

Applying the Custom Filter

To use our CapReader, we chain it into a standard Java character stream pipeline. We instantiate a FileReader to read raw characters from disk, wrap it in a standard BufferedReader for buffering, and then wrap that inside our CapReader to apply the uppercase transformation.

Below is the complete client code showing how to read a file through our custom decorator pipeline:

import java.io.FileReader;
import java.io.IOException;
 
public class CapitalizeRead {
    public static void main(String[] args) {
        String content = textFromFile();
        System.out.println("Uppercase file content:\n" + content);
    }
 
    private static String textFromFile() {
        FileReader f = null;
        try {
            f = new FileReader("wordcheck.txt");
            BufferedReader b = new BufferedReader(f);
            CapReader c = new CapReader(b); // Wrap inside our custom CapReader
            
            String alltext = "";
            String line = "";
            while ((line = c.readLine()) != null) {
                alltext += line + "\n";
            }
            return alltext;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (f != null) {
                try {
                    f.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

Conclusion & Design Benefits

By extending standard reader classes, you can design reusable decorators to perform various tasks—such as decrypting secure files on the fly, striping trailing whitespaces, filtering out spam keywords, or logging stream execution metrics. This maintains a clean separation of concerns and follows the Single Responsibility Principle, demonstrating the elegance of the Decorator pattern.