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.
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#
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:
- Resource waste — DB connection created eagerly, even if query() is never called
- No access control — any SQL (including DROP TABLE) executes unchecked
- No logging — no record of who ran what or when
- No caching — identical queries hit the DB every time
- Security risk — no validation or permission check before executing
Enhanced Code — Proxy Pattern#
// 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#
| Type | Purpose |
|---|---|
| Virtual Proxy | Lazy initialisation of expensive objects |
| Protection Proxy | Access control and permission checks |
| Caching Proxy | Cache results of expensive operations |
| Remote Proxy | Local representative for a remote object |
| Logging Proxy | Record operations without changing real object |
Proxy vs Decorator#
| Proxy | Decorator | |
|---|---|---|
| Intent | Control access to an object | Add behaviour to an object |
| Client awareness | Client doesn't know it's a proxy | Client knows it's decorating |
| Typical use | Security, lazy loading, caching | Feature composition |
| References | Links |
|---|---|
| Article Reference | Refactoring Guru — Proxy |