The Proxy pattern provides a surrogate or placeholder for another object to control access to it. The proxy implements the same interface as the real subject, so clients cannot tell whether they interact with the real object or its stand-in.

Intent and Motivation

Intent: Provide a surrogate or placeholder for another object to control access to it.

Motivation: Creating a large image object is expensive (disk I/O, decoding). A virtual proxy loads the image only when display() is first called. A protection proxy checks user permissions before allowing deleteDocument(). A remote proxy stands in for an object on another server, handling network communication transparently. In all cases, the client uses the same interface — the proxy intercepts, adds logic, and delegates.

Proxies are fundamental to lazy loading, access control, logging, caching, and distributed systems.

Structure (UML-like)

  ┌──────────┐         uses          ┌──────────────┐
│  Client  │ ────────────────────► │   Subject    │ (interface)
└──────────┘                       ├──────────────┤
                                   │ + request()  │
                                   └──────▲───────┘
                                          │
                              ┌───────────┴───────────┐
                              │                       │
                       ┌──────┴──────┐         ┌──────┴──────┐
                       │ RealSubject │         │    Proxy    │
                       ├─────────────┤         ├─────────────┤
                       │ + request() │         │ - realSubject│
                       └─────────────┘         │ + request()  │ (controls access)
                                               └─────────────┘
  

Common proxy types:

  • Virtual Proxy — lazy initialization of expensive objects.
  • Protection Proxy — access control based on permissions.
  • Remote Proxy — local representative for a remote object (RMI, gRPC stub).
  • Caching Proxy — stores results of expensive operations.

Java Example

  // Subject
interface Image {
    void display();
}

// RealSubject — expensive to create
class HighResolutionImage implements Image {
    private final String filename;

    HighResolutionImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename + " from disk...");
    }

    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// Virtual Proxy — lazy loading
class ImageProxy implements Image {
    private final String filename;
    private HighResolutionImage realImage;

    ImageProxy(String filename) {
        this.filename = filename;
    }

    public void display() {
        if (realImage == null) {
            realImage = new HighResolutionImage(filename);
        }
        realImage.display();
    }
}

// Usage — image not loaded until display
Image img = new ImageProxy("photo.jpg");
System.out.println("Proxy created, not loaded yet");
img.display(); // loads and displays
  

JavaScript Example

  // Real subject
const database = {
  query(sql) {
    console.log(`DB executing: ${sql}`);
    return [{ id: 1, name: 'Alice' }];
  }
};

// Caching proxy using ES6 Proxy
function createCachingProxy(target) {
  const cache = new Map();

  return new Proxy(target, {
    get(obj, prop) {
      if (prop === 'query') {
        return function(sql) {
          if (cache.has(sql)) {
            console.log(`Cache hit: ${sql}`);
            return cache.get(sql);
          }
          const result = obj.query(sql);
          cache.set(sql, result);
          return result;
        };
      }
      return obj[prop];
    }
  });
}

const cachedDb = createCachingProxy(database);
cachedDb.query('SELECT * FROM users'); // DB executing
cachedDb.query('SELECT * FROM users'); // Cache hit
  

Real-World Use Cases

Framework / System Usage
Java Dynamic Proxy / CGLIB Spring AOP creates runtime proxies for @Transactional, @Cacheable beans.
Hibernate lazy loading Proxy objects stand in for database entities until accessed.
gRPC / CORBA stubs Remote proxies handle serialization and network transport.
JavaScript Proxy Language-native meta-programming for validation, reactivity (Vue 3), and logging.
Nginx / API Gateway Reverse proxies control access to backend services.
Webpack dev server proxy Forwards frontend requests to backend during development.

Pros and Cons

Pros Cons
Controls access without clients knowing Adds latency and complexity to every method call
Lazy initialization improves startup performance Proxy logic can obscure bugs in the real subject
Enforces security and access policies centrally Remote proxies must handle network failures gracefully
Caching proxies reduce redundant expensive operations Over-proxying simple objects is unnecessary indirection
Works with dependency injection and AOP frameworks Debugging through proxy layers is harder (stack traces)

When to Use vs When NOT to Use

Use when:

  • You need a more versatile or sophisticated reference to an object than a simple pointer.
  • Lazy initialization is important (virtual proxy).
  • Access control, logging, or caching should be transparent to clients.
  • A local representative is needed for a remote object (remote proxy).
  • You want to protect or delay access to a sensitive or expensive resource.

Do NOT use when:

  • The object is cheap to create and access control is unnecessary.
  • You need to add new behavior to an object (use Decorator).
  • You need to change the interface (use Adapter).
  • Direct access is always preferred and proxies add measurable overhead.

Common Mistakes

  1. Proxy vs Decorator — Proxy controls access to an object; Decorator adds new behavior to the same interface.
  2. Eager initialization in a virtual proxy — defeats lazy loading; create the real subject only on first access.
  3. Not implementing the full Subject interface — partial proxies break substitutability.
  4. Ignoring failure modes in remote proxies — network timeouts, retries, and circuit breakers are essential.
  5. Caching proxies with mutable results — cached objects that change externally return stale data.
  • Decorator — same structure, different intent (enrichment vs access control).
  • Adapter — changes interface; Proxy keeps the same interface.
  • Facade — simplifies a subsystem; Proxy represents a single object.
  • Flyweight — both manage object sharing; Proxy controls access, Flyweight shares state.
  • Chain of Responsibility — proxies handle one object; CoR passes requests along a chain.