LESSON 5.7
Proxy
Provide a surrogate or placeholder for another object to control access to it.
15 min read
Real-world analogy
A debit card acts as a proxy for the funds in your physical bank checking account. You present the card at a merchant terminal rather than transporting stacks of physical cash. The card processor verifies your identity, checks your balance (protection proxy), records the transaction history (logging proxy), and communicates across the payment network with the bank's central ledger (remote proxy).
The problem
You are building an analytics dashboard that displays high-resolution satellite imagery. Loading a single raw raster image (GeoRasterImage) takes 3 to 5 seconds, loads 150 MB into RAM, and opens an expensive network socket to a cloud bucket.
When the user opens the dashboard, the UI displays a list of 50 available tiles. If you instantiate all 50 GeoRasterImage objects at startup, the application freezes for 30 seconds and consumes over 7 GB of RAM, even though the user might only click to inspect one or two images:
// Naive implementation: Heavy immediate allocation and loading
class GeoRasterImage {
#filename;
#rawBuffer;
constructor(filename) {
this.#filename = filename;
// Expensive eager loading at construction time!
console.log(`[EAGER] Loading 150 MB raster from cloud: ${filename}...`);
this.#rawBuffer = new Array(150_000_000);
}
display() {
console.log(`Rendering ${this.#filename} (${this.#rawBuffer.length} bytes)`);
}
}
// Startup: Allocating 50 images freezes UI and exhausts memory
const tiles = [
new GeoRasterImage("tile_001.tif"),
new GeoRasterImage("tile_002.tif"),
// ... 48 more images loaded immediately!
];public class Example {
static class GeoRasterImage {
private final String filename;
private final byte[] buffer;
public GeoRasterImage(String filename) {
this.filename = filename;
System.out.println("[EAGER] Loading 150 MB raster from cloud: " + filename);
this
Eagerly instantiating resource-heavy objects during startup degrades performance and exhausts system resources for objects that may never be rendered.
The solution
The Proxy pattern provides a surrogate or placeholder object that implements the exact same interface as the real resource (Subject). The proxy intercepts client calls and defers the expensive instantiation of the RealSubject until the exact moment a method is called (Virtual Proxy), checks security permissions (Protection Proxy), or returns cached results (Caching Proxy).
- Subject (
Image): Common interface implemented by both the real object and the proxy. - RealSubject (
GeoRasterImage): The heavy, expensive, or sensitive component being wrapped. - Proxy (
LazyImageProxy): Holds a reference to theRealSubject, instantiating it lazily and controlling access to its methods. - Client: Interacts with the
Proxyidentically to theRealSubject.
Step-by-step implementation
Step 1: Define the common subject contract
Both the real subject and the proxy must implement this interface.
class Image {
display() {
throw new Error("display() must be implemented");
}
}public class Example {
interface Image {
void display();
}
public static void main(
Step 2: Implement the heavy real subject
The real subject performs the heavy initialization (disk reads, network calls, memory allocation).
Step 3: Implement the virtual proxy with lazy loading
The proxy defers creating the real subject until display() is invoked.
The complete example
Here is the complete runnable example demonstrating lazy loading, caching, and access authorization via Proxies.
--- Operator Access (First Call: Cache Miss) ---
[Cache Miss] Fetching from database for cust_101...
[SQL Query] SELECT * FROM customers WHERE id = 'cust_101' (simulating 50ms latency)
Received: {
id: 'cust_101',
name: 'Alice Developer',
plan: 'Enterprise',
secretToken: '[REDACTED]'
}
--- Operator Access (Second Call: Cache Hit) ---
[Cache Hit] Returning cached record for cust_101
Received: {
id: 'cust_101',
Variations
Types of proxies in practice
- Virtual Proxy (Lazy Loading): Postpones instantiating expensive objects until actually required.
- Protection Proxy (Access Control): Checks caller credentials and authorization tokens before delegating.
- Caching Proxy: Stores return values of deterministic operations in local memory, serving repeated queries without hitting disk or network.
- Remote Proxy: Provides a local stub that handles socket serialization, network transport, and deserialization to an object running in a remote process (e.g. gRPC or Java RMI).
- Logging / Audit Proxy: Records method invocations, arguments, timestamps, and execution duration without modifying business logic.
Dynamic proxies in JavaScript and Java
Modern languages provide meta-programming constructs to build dynamic proxies without generating boilerplate wrapper classes:
- JavaScript
Proxy: The built-innew Proxy(target, handler)allows developers to define traps for property access (get), assignment (set), function calls (apply), and property enumeration (has). Vue 3's entire reactivity engine is powered by JavaScriptProxyobjects. - Java Dynamic Proxies:
java.lang.reflect.Proxy.newProxyInstance()dynamically constructs a proxy implementing any interface at runtime, dispatching all calls to anInvocationHandler.
Where you've already used it
- Hibernate / Spring Data JPA Lazy Loading: When an entity has a
@ManyToOne(fetch = FetchType.LAZY)relationship, Hibernate returns a proxy subclass (via ByteBuddy). The database SQL query is only executed whenentity.getRelated().getName()is invoked. - Vue 3 Reactivity: Vue wraps state objects in JavaScript
Proxyinstances to detect mutations and trigger targeted DOM updates. - Spring
@Transactional& Security: Spring injects AOP proxies around service beans to open database transactions, commit/rollback, and enforce@PreAuthorizesecurity checks. - Reverse Proxies (Nginx, Envoy, Cloudflare): Network-level proxies that terminate TLS, balance load, throttle rates, and cache static assets before forwarding requests to backend origin servers.
Pros and cons
Advantages
- Security & Access Control: Controls access to the real subject transparently without altering client code.
- Resource Optimization: Virtual proxies avoid loading expensive assets into memory until needed.
- Performance Caching: Caching proxies eliminate redundant network or database operations.
- Open/Closed Principle: Introduces new caching, logging, or security policies without touching the real subject.
Disadvantages
- Increased Latency: Adds extra indirection and processing layers to every method invocation.
- Complex Debugging: Dynamic proxies generate obfuscated stack traces that are difficult to debug.
- Initialization Race Conditions: Multi-threaded virtual proxies must synchronize lazy initialization to avoid duplicate object instantiation.
When not to use it
Do not use Proxy if the real subject is already lightweight, cheap to instantiate, and does not require security or caching controls. Adding proxy wrappers around trivial domain POJOs adds unnecessary indirection.
Related patterns
| Pattern | Interface Comparison | Primary Purpose |
|---|---|---|
| Proxy | Identical interface | Controls access, lifecycle, or caching |
| Decorator | Identical interface | Dynamically enhances behavior and features |
| Adapter | Different interface | Bridges incompatible interfaces |
| Facade | New simplified interface | Simplifies an entire subsystem of classes |
Interview questions
How does Proxy differ from Decorator? While both share the exact same interface as the wrapped object, Proxy controls access, lifecycle (lazy initialization), and caching, often creating the RealSubject itself. Decorator adds new business capabilities and responsibilities, usually receiving the component from the client.
How does JavaScript's built-in Proxy object work?
new Proxy(target, handler) intercepts fundamental language operations on the target object using traps (such as get, set, deleteProperty, and apply), enabling reactive data binding, validation, and property virtualization.
What is the difference between static proxies and dynamic proxies in Java?
A static proxy is written by hand at compile time, implementing the target interface explicitly. A dynamic proxy is generated in memory at runtime via java.lang.reflect.Proxy or bytecode manipulation (ByteBuddy), forwarding all method calls to an InvocationHandler.
How does Hibernate implement lazy loading using proxies?
When loading an entity with lazy relationships, Hibernate initializes the field with a dynamic proxy that holds an uninitialized state flag and the entity's ID. When a getter is called on the proxy, it intercepts the call, executes a SELECT query against the database, populates the real entity, and delegates the getter.
Try it
LRU Cache LLD
Build constant-time lookup and eviction with a hash map and a doubly linked list.