The java.io package is one of the classic examples of the **Decorator Design Pattern** in software engineering. By wrapping classes inside other classes, you can dynamically attach new behaviors to input streams without modifying the underlying class structures.

In this guide, we will extend BufferedReader to build a custom decorator—called CapReader—that automatically transforms all text lines to uppercase as they are read.

Illustration of a filter shell converting lowercase words to UPPERCASE words
Real-World Analogy: The Water Filter

Imagine your sink faucet has a basic water pipe (BufferedReader):

  • You buy an attachment filter (CapReader) and screw it onto the faucet.
  • As the 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 doesn't know the filter exists; it just pushes water. The filter decorates the water stream on the fly.

1. Creating the Decorator Class

To build our custom filter, we extend BufferedReader and override its readLine() method. Inside the override, we call super.readLine() to get the raw text, then apply our capitalization logic:

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;
    }
}

2. Applying the Custom Filter

Now, we chain our custom CapReader into the reader pipeline, just like we would with standard Java streams:

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

By extending reader classes, you can design reusable streams that clean inputs, decrypt data on the fly, or format logs automatically. This clean separation of concerns is the essence of the Decorator pattern.