Low Level Design

Proxy Pattern — Control Access to Objects in Java

How the Proxy pattern adds access control, caching, logging, and lazy initialisation to objects without modifying their original class.

August 26, 2026·9 min read

What is the Proxy Pattern?#

The Proxy is a structural design pattern that provides a substitute or placeholder for another object to control access to it. The proxy and the real object share the same interface, so clients can't tell which one they're talking to.

The proxy acts as an intermediary — it can add access control, lazy loading, caching, or logging without touching the real object.

You reach for Proxy when:

  • You need to control access to an object (permissions, authentication)
  • You want lazy initialisation of an expensive resource
  • You need caching to avoid redundant operations
  • You want logging or monitoring without modifying business logic
  • You're working with remote objects (RPC, gRPC, RMI)

Real-World Analogy#

Think of a security guard at a corporate building. The guard is a proxy between visitors and the building's occupants:

  • ✅ Checks credentials before allowing entry
  • 📋 Logs entry times
  • 🚫 Restricts access to certain floors
  • ⏰ Turns away visitors outside office hours

The guard doesn't replace the building or its occupants — they control and monitor access to them.


Class Diagram#


Violation Code — The Problem#

java
class DatabaseService {
    private String connectionString;

    public DatabaseService(String connectionString) {
        this.connectionString = connectionString;
        connectToDatabase(); // ❌ connects immediately, even if never used
    }

    private void connectToDatabase() {
        System.out.println("Connecting to: " + connectionString);
    }

    public void query(String sql) {
        // ❌ No access control — any SQL, by anyone
        System.out.println("Executing: " + sql);
    }
}

// Usage
DatabaseService db = new DatabaseService("prod-db");
db.query("DELETE FROM users"); // ❌ No guard at all

Issues:

  1. Resource waste — DB connection created eagerly, even if query() is never called
  2. No access control — any SQL (including DROP TABLE) executes unchecked
  3. No logging — no record of who ran what or when
  4. No caching — identical queries hit the DB every time
  5. Security risk — no validation or permission check before executing

Enhanced Code — Proxy Pattern#

java
// Subject interface — shared by real object and proxy
public interface Database {
    void query(String sql);
}

// Real subject — the actual database
public class RealDatabase implements Database {
    private final String connectionString;

    public RealDatabase(String connectionString) {
        this.connectionString = connectionString;
        connectToDatabase();
    }

    private void connectToDatabase() {
        System.out.println("Connected to: " + connectionString);
    }

    @Override
    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

// Proxy — controls access to RealDatabase
public class DatabaseProxy implements Database {
    private RealDatabase realDatabase;          // lazy — created on first use
    private final String connectionString;
    private final String userRole;

    public DatabaseProxy(String connectionString, String userRole) {
        this.connectionString = connectionString;
        this.userRole = userRole;
    }

    @Override
    public void query(String sql) {
        // 1. Access control
        if (!hasPermission(sql)) {
            System.out.println("Access denied for role '" + userRole + "': " + sql);
            return;
        }

        // 2. Logging
        System.out.println("[LOG] " + userRole + " executing: " + sql);

        // 3. Lazy initialisation
        if (realDatabase == null) {
            realDatabase = new RealDatabase(connectionString);
        }

        // 4. Delegate to real object
        realDatabase.query(sql);
    }

    private boolean hasPermission(String sql) {
        if (userRole.equals("ADMIN")) return true;
        if (sql.toUpperCase().startsWith("SELECT")) return true;
        return false; // non-admin can't mutate data
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Database adminDb = new DatabaseProxy("prod-db", "ADMIN");
        Database readonlyDb = new DatabaseProxy("prod-db", "READ_ONLY");

        adminDb.query("SELECT * FROM users");     // ✅ allowed
        adminDb.query("DELETE FROM users");       // ✅ allowed

        readonlyDb.query("SELECT * FROM orders"); // ✅ allowed
        readonlyDb.query("DROP TABLE orders");    // ❌ denied — logged
    }
}

Common LLD Problems Using Proxy Pattern#

1. Virtual Proxy — Lazy Loading#

  • Proxy: ImageProxy, DocumentProxy
  • Context: Delay loading large images or PDFs until the user actually views them.

2. Access Control Proxy#

  • Proxy: SecureDatabaseProxy, ProtectedResourceProxy
  • Context: Grant or restrict access to users based on roles or permissions.

3. Caching Proxy#

  • Proxy: CachedWeatherServiceProxy, CachedProductServiceProxy
  • Context: Serve data from an in-memory cache instead of hitting the backend every time.

4. Remote Proxy (RPC / gRPC / RMI)#

  • Proxy: RemoteServiceProxy
  • Context: Client uses a local proxy object that transparently communicates with a remote service.

5. Logging and Monitoring Proxy#

  • Proxy: LoggingProxy, AnalyticsProxy
  • Context: Wrap service calls to log activity or collect metrics without touching core logic.

6. Rate Limiting Proxy#

  • Proxy: RateLimitingProxy
  • Context: Limit the number of requests per second to a service to prevent overload.

7. Connection Pool Proxy#

  • Proxy: ConnectionPoolProxy
  • Context: Manage reusable DB connections — create one only when the pool is empty.

8. Payment Gateway with Fraud Detection#

  • Proxy: PaymentProxy
  • Context: Add pre-checks (fraud detection, logging, validation) before routing to the real payment processor.

Types of Proxies#

TypePurpose
Virtual ProxyLazy initialisation of expensive objects
Protection ProxyAccess control and permission checks
Caching ProxyCache results of expensive operations
Remote ProxyLocal representative for a remote object
Logging ProxyRecord operations without changing real object

Proxy vs Decorator#

ProxyDecorator
IntentControl access to an objectAdd behaviour to an object
Client awarenessClient doesn't know it's a proxyClient knows it's decorating
Typical useSecurity, lazy loading, cachingFeature composition

ReferencesLinks
Article ReferenceRefactoring Guru — Proxy