SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignProxy Pattern
Software DesignStructural Patterns

Proxy Pattern

Provide a surrogate that controls access to another object — add security, caching, or lazy loading transparently.

February 25, 20255 min read
design-patternsjavaproxystructural

One-line Definition

Encapsulates access to an object via a surrogate, allowing you to intercept and control operations dynamically.


Problem

Suppose you have a heavy object like a massive video file or a remote database connection. It consumes vast resources to instantiate or query. If you load it immediately upon startup, you waste resources, but if you scatter "lazy initialization" or caching logic all over your client code, you violate the Single Responsibility Principle. A Proxy solves this by standing in for the real object, looking exactly like it, but handling the caching, security, or lazy-loading behind the scenes.


Real-world Analogy

A credit card is a proxy for your bank account. When you swipe at a store, the card doesn't hand over cash — it checks your credit limit, authenticates you, and then authorizes the payment on your behalf. The store doesn't interact with your bank directly. The credit card controls access to your money.


Structure

  • Client
    • Works with objects strictly via the Subject interface, unaware if it holds a Proxy or the RealSubject.
  • Subject
    • Interface shared by both the real object and the proxy.
  • Proxy
    • Wraps the RealSubject and adds control logic (access checks, caching, lazy init) before or after delegating to the real object.
  • RealSubject
    • The actual object that does the heavy lifting.

Diagram

classDiagram
    class Client {
    }
    class Subject {
        <<interface>>
        +request()
    }
    class Proxy {
        -RealSubject realSubject
        +request()
    }
    class RealSubject {
        +request()
    }
    
    Client --> Subject
    Proxy ..|> Subject
    RealSubject ..|> Subject
    Proxy --> RealSubject : controls access

Code Walkthrough

Notice how the CachingProxy intercepts the execute() call. It checks its cache first, and only delegates to RealDatabase if the result is missing, keeping the client completely unaware of the optimization.

interface DatabaseQuery {
    String execute(String query);
}

class RealDatabase implements DatabaseQuery {
    @Override
    public String execute(String query) {
        System.out.println("Executing heavy query: " + query);
        // Simulate expensive DB call
        return "Result for: " + query;
    }
}

class CachingProxy implements DatabaseQuery {
    private final RealDatabase realDatabase;
    private final Map<String, String> cache = new HashMap<>();
    
    public CachingProxy(RealDatabase realDatabase) {
        this.realDatabase = realDatabase;
    }

    @Override
    public String execute(String query) {
        if (cache.containsKey(query)) {
            System.out.println("Cache hit for: " + query);
            return cache.get(query);
        }
        String result = realDatabase.execute(query);
        cache.put(query, result);
        return result;
    }
}

class Main {
    public static void main(String[] args) {
        RealDatabase realDb = new RealDatabase();
        DatabaseQuery db = new CachingProxy(realDb);
        
        System.out.println(db.execute("SELECT * FROM users"));  // DB call
        System.out.println(db.execute("SELECT * FROM users"));  // Cache hit
        System.out.println(db.execute("SELECT * FROM orders")); // DB call
    }
}

Bad vs Good

Bad Approach

Problems

  • The client handles the caching logic directly, violating the Single Responsibility Principle.
  • If multiple clients query the database, the caching logic is duplicated everywhere.
class ClientService {
    private final RealDatabase db = new RealDatabase();
    private final Map<String, String> cache = new HashMap<>();

    public String getUserData(String query) {
        if (cache.containsKey(query)) {
            return cache.get(query);
        }
        String result = db.execute(query);
        cache.put(query, result);
        return result;
    }
}

Better Approach

Improvements

  • Client relies entirely on the interface.
  • Caching, security, or logging can be added via the Proxy, keeping the client code clean and focused on business logic.
// Client simply requests data
DatabaseQuery db = new CachingProxy(new RealDatabase());
String data = db.execute("SELECT * FROM users"); // Caching handled invisibly

Pros vs Cons

ProsCons
Controls access to an object without modifying client codeAdds an extra layer of indirection
Centralizes cross-cutting concerns (security, caching, logging)Can introduce latency in performance-critical paths
Follows Open/Closed Principle — proxy acts as a non-invasive wrapperProxy and real object must strictly share the same interface
Manages the lifecycle of heavy objects (lazy loading)Can become difficult to trace logic flow (too much "magic")
Fully transparent to the client

When to Use

  • Lazy initialization (Virtual Proxy): You have a heavy object you want to delay creating until it's actually needed.
  • Access control (Protection Proxy): You want specific clients to have different permissions when calling a method.
  • Local execution of remote service (Remote Proxy): You want to hide network communication logic from the client.
  • Caching (Caching Proxy): You want to cache results of expensive operations.

When Not to Use

  • Direct access to the object is inexpensive and requires no special rules or caching.
  • Adding a proxy would just add an unnecessary layer of wrappers with no distinct behavioral changes.

Real-world Examples

  • java.lang.reflect.Proxy (Java's built-in dynamic proxy mechanism)
  • Spring AOP (Aspect-Oriented Programming uses proxies for @Transactional, @Cacheable, etc.)
  • Hibernate's lazy-loaded entity collections.

Key Takeaway

The Proxy Pattern intercepts access to an object through a surrogate that shares the exact same interface, letting you add security, caching, lazy loading, or networking logic completely invisibly. It is the backbone of modern framework "magic" (like Spring AOP), but avoid creating proxies if direct access works fine.

PreviousVisitor PatternNextBuilder Pattern