skip to content

Decorator in Java

The java.io hierarchy is the canonical Decorator: BufferedInputStream wraps any InputStream, and Collections.synchronizedList and unmodifiableList wrap collections. Interviewers care that you can explain why wrapping order matters.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is the Decorator pattern, and where does the JDK use it (give the canonical example)?

level: juniorimportance: must knowfreq 70%

answer

  1. Same type, holds a reference, delegates, adds behavior
  2. Composition not subclassing -> avoids class explosion
  3. java.io: BufferedInputStream wraps InputStream
  4. Collections.synchronizedList / unmodifiableList wrap a List
  5. Stackable and substitutable at runtime

basics

~20 s

Decorator wraps an object to add behavior without changing its class. It implements the same type as what it wraps and forwards calls to it, adding extra work. The classic JDK example is java.io: BufferedInputStream wraps an InputStream to add buffering.

solid answer

~40 s

The Decorator pattern attaches extra responsibilities to an object dynamically by wrapping it in another object that implements the same interface (or extends the same abstract type). The wrapper holds a reference to the wrapped component, forwards method calls to it, and adds behavior before or after delegating. Because both share a type, a decorator is interchangeable with the thing it decorates, and decorators can be stacked. The textbook JDK example is the java.io stream hierarchy: a FileInputStream provides raw bytes, BufferedInputStream wraps it to add buffering, and you could further wrap that with a DataInputStream to add typed reads. Collections.synchronizedList and Collections.unmodifiableList are also decorators: each returns a List that wraps the original and adds locking or read-only enforcement while delegating the actual storage.

code

java · 11 lines
java
// FileInputStream = concrete component (raw bytes from disk)
// BufferedInputStream = decorator adding a buffer
// DataInputStream    = decorator adding typed reads
try (DataInputStream in =
         new DataInputStream(
             new BufferedInputStream(
                 new FileInputStream("data.bin")))) {
    int id = in.readInt();      // typed read (DataInputStream)
    double v = in.readDouble(); // buffered under the hood
}
// All three are InputStreams: each forwards read() to the one it wraps.

go deeper

for a junior

Can state the one-liner: Decorator wraps an object of the same type to add behavior, and name BufferedInputStream over FileInputStream as the example.

for a middle

Explains the structural rule (same Component type, holds a reference, delegates, adds behavior), why composition beats subclassing, and names both java.io and the Collections wrappers.

for a senior

Contrasts Decorator with Proxy/Adapter, discusses FilterInputStream as the base decorator, and reasons about substitutability and stacking order.

for a principal

Frames Decorator against the open/closed principle and interface-driven design, weighs its cost (many small objects, hard-to-debug deep stacks) and notes alternatives like the java.io transparent-decorator critique.

## The problem Decorator solves Suppose you have a basic capability — say, reading bytes from a file — and you want to optionally add features: buffering, decompression, decryption, typed reads, counting bytes. If you tried to express every combination with subclassing, you'd get a class explosion (`BufferedGzipDataFileInputStream`, etc.). Decorator avoids this by letting you **compose** features at runtime: each feature is its own wrapper, and you stack only the ones you need. ## Key terms (defined from scratch) - **Component**: the common type (interface or abstract class) that both the real object and the wrappers share. In java.io this is `InputStream`. - **Concrete component**: the base object that does the real work. e.g. `FileInputStream` (reads bytes from a file). - **Decorator**: an object that *implements the same Component type* and *holds a reference to another Component* (the thing it wraps). It forwards (delegates) calls to the wrapped object and adds behavior before/after. In java.io the base decorator is `FilterInputStream`, and concrete decorators are `BufferedInputStream`, `DataInputStream`, `GZIPInputStream`, etc. - **Delegation / forwarding**: calling the same method on the wrapped object. e.g. `BufferedInputStream.read()` ultimately calls the underlying stream's `read()` to refill its buffer. ## Why "same type" matters Because a decorator *is-a* Component, code that accepts a Component (`void process(InputStream in)`) works identically whether you hand it a raw `FileInputStream` or one wrapped five layers deep. The caller is decoupled from how many features are attached. This is the defining structural rule: a decorator must be **substitutable** for what it wraps. ## The canonical JDK example: java.io streams ```java InputStream in = new BufferedInputStream(new FileInputStream("data.bin")); ``` Here `FileInputStream` is the concrete component (raw bytes). `BufferedInputStream` is a decorator that adds an in-memory buffer so the program makes fewer, larger reads from disk. Both are `InputStream`s, so `in.read()` is polymorphic. You can stack further: wrap the buffered stream in a `DataInputStream` to read `int`/`double`/`UTF` directly, or a `GZIPInputStream` to decompress on the fly. The order of wrapping determines the order features apply. ## Collections decorators The JDK also decorates collections: - `Collections.synchronizedList(list)` returns a `List` that wraps the original and makes each method `synchronized` (thread-safe) while delegating storage to the wrapped list. - `Collections.unmodifiableList(list)` returns a `List` that delegates all reads but throws `UnsupportedOperationException` on any mutation — a read-only decorator. These add behavior (locking, immutability) without changing the original list's class, exactly the Decorator intent. ## What you should remember Decorator = same type + holds a reference + delegates + adds behavior, composed at runtime and stackable. java.io is the interview go-to example; the Collections wrappers are the second.

  • Why does Decorator use composition instead of inheritance to add features?
    Inheritance would require a separate subclass for every feature combination (class explosion) and is fixed at compile time. Composition lets you stack any subset of features at runtime, and each feature is one small, reusable wrapper.
  • Is Collections.unmodifiableList a real copy of the list?
    No. It's a view that wraps the original and delegates reads to it; changes to the underlying list are visible through the wrapper. It only blocks mutating methods, throwing UnsupportedOperationException.

saying these in an interview costs you the question

  • Saying Decorator changes the class of the wrapped object (it wraps, never modifies it)
  • Confusing it with inheritance — decorators use composition/delegation, not 'extends the concrete class'
  • Claiming the decorator does not implement the same interface (then it wouldn't be substitutable)
  • Citing Adapter or Proxy as the io example — io is Decorator

context

open as a page

When you stack java.io stream decorators, why does the order of wrapping matter? Give an example where wrong order causes a bug or poor performance.

level: middleimportance: should knowfreq 55%

basics

~20 s

Each wrapper feeds bytes through the one it wraps, so order decides what happens in what sequence. Put buffering closest to the raw source. If you wrap in the wrong order, you can lose the buffer's benefit (slow) or transform bytes at the wrong stage.

open as a page

Write a custom Decorator over an existing interface (e.g. a List or an InputStream) that adds behavior, and explain the delegation mechanics.

level: middleimportance: should knowfreq 45%

basics

~20 s

Implement the same interface as the thing you wrap, store a reference to it, and forward each method to that reference, adding your extra behavior in the methods you care about. For example, a List wrapper that counts how many times add() is called before delegating to the real list.

open as a page

How does Decorator differ from Proxy and Adapter? Use JDK examples to distinguish them.

level: seniorimportance: should knowfreq 60%

basics

~20 s

All three wrap an object, but with different intent. Decorator adds behavior and keeps the same interface (BufferedInputStream over InputStream). Adapter changes the interface to a different one (Arrays.asList: array to List). Proxy controls access without changing behavior (java.lang.reflect.Proxy, lazy/remote/security gates).

open as a page

What are the design trade-offs of the Decorator pattern, and what criticisms apply to the java.io stream API specifically?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Decorator gives flexible, runtime-composable features without a class explosion, following open/closed. The cost: many small objects, deep stacks that are hard to read and debug, easy-to-misorder wrapping, and lost object identity. The java.io API is often criticized for confusing, verbose wrapping with too many similar classes.

open as a page