Back to Design Patterns
Structural Pattern

Proxy Pattern

Provide a substitute or placeholder for another object to control access to it — adding lazy loading, caching, logging, or access control transparently.

Intermediate10 min read

What is the Proxy Pattern?

The Proxy pattern provides a stand-in object that controls access to another object (the real subject). The proxy implements the same interface as the real subject, so the client can't tell the difference — but the proxy intercepts every call and can add behaviour before or after forwarding.

Think of a bank card: the card isn't your bank account — it's a proxy. It controls access (PIN check), logs transactions, enforces limits, and only then lets the operation through to the real account.

The key distinction from Decorator: the Proxy controls access to the subject, while Decorator adds behaviour. Proxy often manages the subject's lifecycle (creating it lazily). Decorator receives the subject from outside.

Key Idea: Same interface as the real object, but with a gatekeeper in front. Intercept calls to add caching, logging, access control, or lazy initialisation — transparently.

Types of Proxy

Virtual Proxy

Virtual Proxy — Lazy Loading

Creates the real object only when first needed. Useful when the real object is expensive to create (large file, DB connection). The proxy stands in until the real object is actually required.

Protection Proxy

Protection Proxy — Access Control

Controls who can call which operations on the real object. Checks permissions before forwarding calls. The real object has no auth logic — the proxy is the gatekeeper.

Caching Proxy

Caching Proxy — Result Caching

Caches results of expensive operations. Returns cached data for repeated identical requests, only calling the real object on a cache miss or after TTL expires.

Logging Proxy

Logging Proxy — Audit Trail

Records every call made to the real object — method name, arguments, timing, return value. The real object stays clean of logging concerns.

Remote Proxy

Remote Proxy — Network Abstraction

Represents an object that lives in a different process or machine. Handles serialisation, network communication, and error handling — the client thinks it's calling a local object.

Proxy vs Decorator — The Difference

AspectProxyDecorator
PurposeControl access to the subjectAdd behaviour to the subject
Subject lifecycleProxy may create/manage itDecorator receives it from outside
StackingUsually one proxy layerOften stacked multiple times
Client intentAccess control / transparencyFeature extension

Implementation

// Proxy Pattern — Virtual + Protection + Caching Proxy

// ─── Subject interface ────────────────────────────────────────
interface ImageService {
    String loadImage(String filename);
    void   deleteImage(String filename);
}

// ─── Real Subject ─────────────────────────────────────────────
class RealImageService implements ImageService {
    public String loadImage(String filename) {
        System.out.println("[RealService] Loading from disk: " + filename);
        // Simulate expensive disk I/O
        return "ImageData:" + filename;
    }
    public void deleteImage(String filename) {
        System.out.println("[RealService] Deleted: " + filename);
    }
}

// ─── Virtual + Caching Proxy ──────────────────────────────────
class CachingImageProxy implements ImageService {
    private RealImageService realService;
    private java.util.Map<String, String> cache = new java.util.HashMap<>();

    private RealImageService getService() {
        if (realService == null) {
            System.out.println("[Proxy] Lazily initialising RealImageService...");
            realService = new RealImageService();  // created only on first use
        }
        return realService;
    }

    @Override
    public String loadImage(String filename) {
        if (cache.containsKey(filename)) {
            System.out.println("[Proxy] Cache HIT: " + filename);
            return cache.get(filename);
        }
        System.out.println("[Proxy] Cache MISS: " + filename);
        String data = getService().loadImage(filename);
        cache.put(filename, data);
        return data;
    }

    @Override
    public void deleteImage(String filename) {
        cache.remove(filename);                    // invalidate cache
        getService().deleteImage(filename);
    }
}

// ─── Protection Proxy ─────────────────────────────────────────
class ProtectedImageProxy implements ImageService {
    private ImageService inner;
    private String       userRole;

    public ProtectedImageProxy(ImageService inner, String userRole) {
        this.inner    = inner;
        this.userRole = userRole;
    }

    @Override
    public String loadImage(String filename) {
        return inner.loadImage(filename);   // all users can read
    }

    @Override
    public void deleteImage(String filename) {
        if (!userRole.equals("ADMIN")) {
            throw new SecurityException("Only admins can delete images!");
        }
        inner.deleteImage(filename);
    }
}

// ─── Client ───────────────────────────────────────────────────
public class App {
    public static void main(String[] args) {
        ImageService cache = new CachingImageProxy();

        System.out.println(cache.loadImage("photo.jpg")); // miss → loads
        System.out.println(cache.loadImage("photo.jpg")); // hit  → cached
        System.out.println(cache.loadImage("logo.png"));  // miss → loads

        // Wrap cache in a protection proxy
        ImageService adminView = new ProtectedImageProxy(cache, "ADMIN");
        ImageService userView  = new ProtectedImageProxy(cache, "USER");

        adminView.deleteImage("photo.jpg"); // allowed
        try {
            userView.deleteImage("logo.png");   // throws
        } catch (SecurityException e) {
            System.out.println("[Access Denied] " + e.getMessage());
        }
    }
}

When to Use Proxy

Lazy Initialisation of Heavy Objects

When a resource-heavy object should only be created on first use — database connections, large file loads, expensive calculations.

VirtualImageProxy, LazyConnectionProxy

Access Control / Security

When only certain users or roles should be able to call specific methods — the proxy checks permissions before forwarding.

ProtectedFileProxy, RoleBasedServiceProxy

Transparent Caching

When results of expensive service calls should be cached without changing the service interface — the client code stays unchanged.

CachedAPIProxy, MemoizedQueryProxy

Remote Object Access

When representing a remote service locally — the proxy handles serialisation, network calls, and error handling transparently.

gRPC stubs, REST client proxies, RMI

Logging / Monitoring

When you need to log all method calls, track performance, or capture metrics without cluttering business logic.

LoggingServiceProxy, MetricsProxy

Pros & Cons

Advantages

  • Control access without changing the real subject
  • Add cross-cutting concerns (caching, logging, auth) transparently
  • Open/Closed — extend behaviour without modifying existing code
  • Can manage subject lifecycle (lazy creation, cleanup)
  • Client code unchanged when switching real vs proxy

Disadvantages

  • Extra indirection adds slight latency
  • Response from real service may be delayed (lazy init)
  • More classes to maintain
  • Caching proxies can return stale data if not invalidated correctly

Real-World Examples

Spring AOP (Java)

Spring's Aspect-Oriented Programming wraps beans in dynamic proxies. @Transactional, @Cacheable, @Secured annotations all work by creating a proxy around the bean that intercepts calls.

Python's unittest.mock

Mock objects are Proxy implementations — they stand in for real objects during tests, recording all calls made to them and returning configured values.

JavaScript ES6 Proxy

JavaScript has Proxy as a first-class language feature. Vue 3's reactivity system uses Proxy to intercept property reads/writes and trigger DOM updates automatically.

Hibernate Lazy Loading (Java)

Hibernate uses virtual proxies for lazy-loaded relationships. An @OneToMany collection is a proxy object — the DB query only fires when you actually access the collection's contents.

What's Next?

Explore other Structural patterns: