Decorator & Proxy in Java
Wrapping to add behavior (java.io streams) vs controlling access.
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();
}
}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());
}[hi]! [hi!][hi!] [hi]![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();
}
}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.
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.
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
- Same interface as the wrapped object, so callers don't notice
- Decorator: adds responsibilities, stackable (java.io streams)
- Proxy: controls access (lazy, remote, security, caching)
- 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.
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());
}- tea+ice+milk
- +ice+milk+tea
- tea
- 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();
}
}- Decorator
- Proxy (virtual proxy)
- Adapter
- 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.