Proxy Pattern explained simply
Sometimes you don't want or can't allow direct access to an object. Maybe it's expensive to create, needs special permissions, or you want to control access in some way. This is where the Proxy Pattern shines.
The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.
What is the Proxy Pattern?
At its core, the Proxy Pattern:
- Controls access to another object (the "real subject").
- Can add additional behavior (e.g., lazy initialization, security checks, logging).
- Makes working with heavy or sensitive objects more efficient and safe.
Think of a security guard at a door. You can't directly enter — you must go through the guard (the proxy).
Here is a UML class diagram showing how the pieces fit together. The Client talks to the WeatherService interface and never knows whether it is dealing with the real service or the proxy.
<mxfile>
<diagram name="Proxy Pattern">
<mxGraphModel dx="1024" dy="768" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="500">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- WeatherService interface -->
<mxCell id="2" value="<b>&lt;&lt;interface&gt;&gt;</b>
<b>WeatherService</b>
——————————————
+ getWeather(city: String): String" style="shape=rectangle;whiteSpace=wrap;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;fontSize=13;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="270" y="30" width="300" height="90" as="geometry"/>
</mxCell>
<!-- RealWeatherService -->
<mxCell id="3" value="<b>RealWeatherService</b>
——————————————
+ getWeather(city: String): String" style="shape=rectangle;whiteSpace=wrap;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;fontSize=13;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="80" y="220" width="300" height="80" as="geometry"/>
</mxCell>
<!-- WeatherServiceProxy -->
<mxCell id="4" value="<b>WeatherServiceProxy</b>
——————————————
- realService: WeatherService
- cache: Map&lt;String, CacheEntry&gt;
——————————————
+ getWeather(city: String): String" style="shape=rectangle;whiteSpace=wrap;align=left;verticalAlign=top;spacingLeft=8;spacingTop=4;fontSize=13;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="460" y="200" width="310" height="120" as="geometry"/>
</mxCell>
<!-- Client -->
<mxCell id="5" value="<b>Client</b>" style="shape=rectangle;whiteSpace=wrap;align=center;verticalAlign=middle;fontSize=13;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="360" y="400" width="120" height="50" as="geometry"/>
</mxCell>
<!-- RealWeatherService implements WeatherService -->
<mxCell id="6" style="endArrow=block;endFill=0;endSize=14;strokeWidth=1.5;dashed=1;" edge="1" source="3" target="2" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- WeatherServiceProxy implements WeatherService -->
<mxCell id="7" style="endArrow=block;endFill=0;endSize=14;strokeWidth=1.5;dashed=1;" edge="1" source="4" target="2" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- WeatherServiceProxy holds reference to RealWeatherService -->
<mxCell id="8" value="delegates to" style="endArrow=open;endSize=12;strokeWidth=1.5;dashed=1;fontSize=11;" edge="1" source="4" target="3" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Client uses WeatherService (via Proxy) -->
<mxCell id="9" value="uses" style="endArrow=open;endSize=12;strokeWidth=1.5;fontSize=11;" edge="1" source="5" target="4" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Real-Life Analogy
Imagine you want to visit a celebrity. You can't just walk into their home. You have to talk to their agent (proxy), who controls access, manages schedules, and sometimes answers simple questions on their behalf.
Proxy Pattern in Code (Java)
Let's build something you will actually run into in the real world — an API rate-limiting and caching proxy for a weather service.
First, the interface that both the real service and the proxy will implement:
public interface WeatherService {
String getWeather(String city);
}
The real object that hits an external API. In production this would be an HTTP call, but the idea is the same — it is slow and you get charged per request:
public class RealWeatherService implements WeatherService {
@Override
public String getWeather(String city) {
// Imagine this calls an external REST API
System.out.println("[API] Calling external weather API for: " + city);
return "Sunny, 25°C in " + city;
}
}
Now the proxy. It caches results and only allows one real API call per city per minute. Every other call within that window just returns the cached response:
import java.util.HashMap;
import java.util.Map;
public class WeatherServiceProxy implements WeatherService {
private final WeatherService realService;
private final Map<String, CacheEntry> cache = new HashMap<>();
private final long ttlMillis;
public WeatherServiceProxy(WeatherService realService, long ttlMillis) {
this.realService = realService;
this.ttlMillis = ttlMillis;
}
@Override
public String getWeather(String city) {
CacheEntry entry = cache.get(city);
if (entry != null && !entry.isExpired(ttlMillis)) {
System.out.println("[CACHE] Returning cached result for: " + city);
return entry.data;
}
// Cache miss or expired — call the real service
String result = realService.getWeather(city);
cache.put(city, new CacheEntry(result));
return result;
}
private static class CacheEntry {
final String data;
final long timestamp;
CacheEntry(String data) {
this.data = data;
this.timestamp = System.currentTimeMillis();
}
boolean isExpired(long ttlMillis) {
return System.currentTimeMillis() - timestamp > ttlMillis;
}
}
}
And use it like this:
public class MainProgram {
public static void main(String[] args) throws InterruptedException {
WeatherService service = new WeatherServiceProxy(
new RealWeatherService(),
60_000 // 1 minute TTL
);
// First call — hits the real API
System.out.println(service.getWeather("London"));
// Second call for the same city — served from cache
System.out.println(service.getWeather("London"));
// Different city — hits the real API again
System.out.println(service.getWeather("Tokyo"));
}
}
Output:
[API] Calling external weather API for: London
Sunny, 25°C in London
[CACHE] Returning cached result for: London
Sunny, 25°C in London
[API] Calling external weather API for: Tokyo
Sunny, 25°C in Tokyo
Notice how the client code has no idea it is talking to a proxy. It just calls getWeather() on a WeatherService. The caching and rate-limiting happen transparently behind the interface. That is the whole point.
Key Components
- Subject Interface (
WeatherService): Common interface for both the real service and the proxy. - RealSubject (
RealWeatherService): The actual object that does the heavy lifting — in this case, calling an external API. - Proxy (
WeatherServiceProxy): Sits in front of the real service, adding caching and rate-limiting without the client ever knowing.
When to Use the Proxy Pattern?
- When accessing a real object is resource-intensive (e.g., external API calls, database connections).
- When you need additional access control (e.g., authentication).
- When you want to add lazy initialization to heavy objects.
- When you need remote proxies (access objects over the network).
Types of Proxies
- Virtual Proxy: Controls access to a resource that is expensive to create.
- Protection Proxy: Controls access based on permissions.
- Remote Proxy: Represents an object in a different address space (e.g., server).
- Smart / Caching Proxy: Adds extra functionality like caching, rate-limiting, logging, or reference counting. Our
WeatherServiceProxyabove is a textbook example — it wraps the real service with a TTL-based cache so the expensive API call only happens when it actually needs to.
Advantages
- Adds control over the real object without changing its code.
- Supports lazy initialization and performance optimizations.
- Adds extra responsibilities like security, caching, or logging.
Disadvantages
- Adds complexity to the system.
- Can create too many layers of indirection if overused.
- Might introduce performance overhead in simple cases.
Real-World Use Cases
- API rate-limiting and caching: Exactly what we built above — wrap an external service call with a proxy that caches responses and enforces request limits.
- Database connections: Open only when necessary.
- Security layers: Check permissions before allowing access.
- Network communication: Stub objects that behave like the real remote object.
- File systems: Load files lazily.
Final Thoughts
The Proxy Pattern is all about control — controlling access, controlling initialization, and even controlling additional behavior without touching the real object.
Whenever you need an object to stand in for another, think about using a Proxy.
Happy proxying!