🏛️ Design Principles & Patterns · Advanced

Decorator & Proxy in Java

Wrapping to add behavior (java.io streams) vs controlling access.

🧩 The mysteryCoffee, plus milk, plus foam, plus caramel: each topping wraps the last, and it's still a coffee. Java's I/O library is built exactly like that.

Wrap it, keep the interface

A Decorator wraps an object that has the same interface and adds behavior around it. Callers can't tell the difference, so decorators can be stacked in any combination, like coffee toppings.

interface Text { String get(); }
record Upper(Text t) implements Text {
    public String get() {
        return t.get().toUpperCase();
    }
}
🔮 Predict it

Which layer runs first?

wrap and bang each return a new T that wraps the one they're given. What does this print?

interface T { String get(); }
T wrap(T t) {
    return () -> "[" + t.get() + "]";
}
T bang(T t) { return () -> t.get() + "!"; }
void main() {
    T hi = () -> "hi";
    IO.println(bang(wrap(hi)).get());
    IO.println(wrap(bang(hi)).get());
}
  1. [hi]! [hi!]
  2. [hi!] [hi]!
  3. [hi]! [hi]!
Show the answer

The outer decorator calls the inner one first, then works on its result. bang(wrap(hi)): wrap gives "[hi]", then bang adds "!". Swap the layers and you get "[hi!]". Decorators apply from the inside out, so stacking order matters.

Decorators in java.io

BufferedReader **extends Reader** and wraps another Reader, adding buffering and readLine(). Because it's still a Reader, you can wrap it again. Streams stack the same way: buffering, then decompression, on top of a file.

var in = new BufferedReader(
    new FileReader(file));
var z = new GZIPInputStream(
    new BufferedInputStream(
        new FileInputStream(file)));

Proxy: same face, controls access

A Proxy also has the same interface, but its job is to control access to the real object: create it lazily, cache results, check permissions, forward calls to another server, or log. This *virtual proxy* only builds the costly image on first draw().

class LazyImage implements Image {
    private RealImage real;
    private final String file;
    LazyImage(String f) { file = f; }
    public void draw() {
        if (real == null)
            real = new RealImage(file);
        real.draw();
    }
}
🤔 Think first

Same structure, different name?

Decorator and Proxy both wrap an object with the same interface. So how do you tell them apart?

Think about it, then reveal the answer

By intent. A decorator adds behavior and is stacked freely by the client. A proxy controls access to the real object and often manages its lifecycle. Collections.unmodifiableList is a *protection proxy*: same List interface, but writes are refused.

⚠️ The trap

Changing the interface isn't a proxy

Turning a legacy XML API into a JSON interface changes the interface, so it's an Adapter, not a proxy. Proxies and decorators keep the interface identical. Also a myth: "Decorator uses inheritance, Proxy uses interfaces." Both are about wrapping.

💼 In the real world

Proxies you already use

Spring implements @Transactional and other annotations with runtime proxies that wrap your beans. Logging, metrics and retry wrappers are decorators. Knowing which is which helps when a stack trace is full of generated proxy classes.

Key takeaways

  1. Same interface as the wrapped object, so callers don't notice
  2. Decorator: adds responsibilities, stackable (java.io streams)
  3. Proxy: controls access (lazy, remote, security, caching)
  4. Collections.unmodifiableList acts as a protection proxy

💡 Decorator is gift-wrapping a box in layers; a proxy is the receptionist who decides if you see the boss.

🤯 Did you know?

Java 1.3 added java.lang.reflect.Proxy, which builds proxy classes at runtime for any interface. Many frameworks rely on it to wrap your objects without you writing a single wrapper.

Practice questions

What does this print?

interface T { String get(); }
record Add(T t, String x) implements T {
    public String get() { return t.get() + x; }
}
void main() {
    T t = () -> "tea";
    T d = new Add(new Add(t, "+milk"), "+ice");
    System.out.println(d.get());
}
  1. tea+ice+milk
  2. +ice+milk+tea
  3. tea
  4. tea+milk+ice
Check your answer

tea+milk+ice. The outer decorator calls the inner one first and appends after it, so layers apply from the inside out.

Which pattern is this?

class LazyImage implements Image {
    private RealImage real;
    private final String file;
    LazyImage(String f) { file = f; }
    public void draw() {
        if (real == null)
            real = new RealImage(file);
        real.draw();
    }
}
  1. Decorator
  2. Proxy (virtual proxy)
  3. Adapter
  4. Facade
Check your answer

Proxy (virtual proxy). LazyImage has the same interface as the real image but controls when it gets created. It adds no new drawing behavior, so its intent is access control, not decoration.

Next: your code wants pay(Money), but the vendor SDK only offers charge(long cents, String currency). Who translates?