Proxy: Virtual, Protection & Caching
The Problem It Solves
Some objects are expensive to create (a 50 MB report image), dangerous to expose (payment service behind authz), or slow to recompute (exchange rates). Clients shouldn’t pay those costs — or take those risks — directly. A proxy is a stand-in with the same interface that intercepts calls and adds control before the real object is touched.
same interface
Client ──────────────► Proxy ── gate/lazy/cache ──► RealSubject
believes it holds decides whether/ the expensive or
the real thing when/how to forward protected target
The Three Flavors
| Flavor | Interception | Example |
|---|---|---|
| Virtual proxy | Delays creation until first use | Image proxy loads 50 MB photo only when rendered |
| Protection proxy | Checks caller rights | BankAccountProxy rejects withdrawals unless caller.isAdmin() |
| Caching proxy | Returns stored result instead of recomputing | Rate API memoized for TTL window |
Mechanics — Virtual + Protection combined
interface Report { byte[] render(); }
class HeavyReport implements Report { // REAL subject
public byte[] render() {
return computeExpensiveReport(); // seconds of work
}
}
class ProtectedLazyReportProxy implements Report { // PROXY
private final User caller;
private volatile HeavyReport real; // lazy, double-checked
ProtectedLazyReportProxy(User caller) { this.caller = caller; }
@Override public byte[] render() {
if (!caller.hasRole("ANALYST")) // protection layer
throw new SecurityException("denied");
if (real == null) // virtual layer
synchronized (this) {
if (real == null) real = new HeavyReport();
}
return real.render();
}
}
The client holding Report cannot tell which it has — substitution transparency is what makes proxies composable into existing call graphs without edits.
Caching Proxy Shape
class CachedRateProxy implements ExchangeRates {
private final ExchangeRates real; // remote/slow source
private final Map<CurrencyPair, CachedValue> cache = new ConcurrentHashMap<>();
private static final long TTL_MS = 60_000;
public BigDecimal rate(CurrencyPair pair) {
CachedValue c = cache.get(pair);
if (c == null || c.isOlderThan(TTL_MS)) // stale → recompute
cache.put(pair, c = new CachedValue(real.rate(pair)));
return c.value();
}
}
TTL choice is a consistency trade-off: shorter = fresher + more load on real; longer = cheaper + staler answers.
vs Decorator (the eternal exam question)
Both wrap same-interface objects. The difference is intent:
- Decorator adds behavior/enrichment, freely stackable.
- Proxy controls access to the real object’s lifecycle/availability — creation gating, security, remoteness. Usually terminal (you don’t stack five proxies).
Real-World Sightings
- Spring
@Transactional/@Cacheable/@PreAuthorize— ACG proxies woven at runtime. - Hibernate lazy-loading entities: your
Orderfield may be a proxy until first touch. - RPC stubs (gRPC clients) are remote proxies; CDNs are caching proxies at network scale.
- JDK
java.lang.reflect.Proxy— dynamic proxy generation used by every DI/AOP framework.
Trade-offs & Pitfalls
- Indirection hides cost location (“why is this ‘simple getter’ hitting the network?” — because it’s a remote proxy).
- Lazy initialization + concurrency needs synchronization care (double-checked pattern above).
- Overriding
equals/hashCodeacross proxies vs real subjects breaks collections — frameworks spend surprising effort here.
Interview Framing
- Naming all three flavors plus one real framework example each (
@PreAuthorize, Hibernate lazy, gRPC stubs) closes the knowledge arc interviewers probe for.
Premium Content
Unlock Proxy and all premium lessons with a subscription.
From ₹199.99/year — See plans