Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.
Umm…what?? 🤔
You have probably heard of proxy servers in networking.
In simple terms, a proxy server acts as an intermediary between users and the websites they access. When you use a proxy, your request doesn’t go directly to the destination website. Instead, it first goes to the proxy server, which forwards the request on your behalf. The response then returns to the proxy, which passes the data back to you.

At first glance, this might seem unnecessary 🤷. If the proxy simply forwards requests and responses, why not connect directly to the website ??
Because forwarding traffic is only part of the story.
Proxy servers often act as firewalls, enforce web filtering policies, manage network connections, and cache frequently requested data to improve performance. A well-configured proxy can protect internal systems from malicious traffic while also optimizing and controlling outbound requests. In other words, it adds a powerful layer of control, security, and efficiency between the client and the destination.
So what does this have to do with the Proxy Design Pattern 🤔??
The Proxy Design Pattern applies the same core idea in software design.
Instead of sitting between a user and a website, a proxy object sits between a client and a real subject.
It controls access, adds behavior (like lazy loading, logging, security checks, or caching), and defers expensive operations until they are truly necessary.
Just like a network proxy, a design pattern proxy isn’t merely a middleman – it’s a smart gatekeeper
Let’s understand this with an example.
Let us suppose we need to designing a cloud storage system like Google Drive or Dropbox that allows users to
- View file metadata
- Preview file
For this let us say we have the below class:
public class RealCloudFile {
private final String fileName;
private File downloadedFile;
// Initialization of CloudFile is an expensive operation
// As it involves actually downloading the file
// From a storage bucket
public RealCloudFile(String fileName) {
this.fileName = fileName;
downloadFile();
}
private void downloadFile() {
System.out.println("Downloading file: " + fileName);
simulateDelay(); // Just to show downloading is expensive
// Simulate file content
downloadedFile = new File(fileName);
}
// Lightweight operation
public void showMetadata() {
long sizeInBytes = downloadedFile.length();
System.out.println("File:" + fileName);
System.out.println("Size: " + sizeInBytes + " bytes");
System.out.println("Last Modified: " + downloadedFile.lastModified());
}
// Returns the downloaded file
public File preview() {
System.out.println("Showing preview of " + fileName);
return downloadedFile;
}
// Simulate a 1-second delay to show an expensive operation
private void simulateDelay() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}Now in order to use this class we can have a test drive like
public class TestDriveCloudFile {
public static void main(String[] args) {
System.out.println("Creating CloudFile instance...");
RealCloudFile cloudFile = new RealCloudFile("example.txt");
System.out.println("Displaying metadata:");
cloudFile.showMetadata();
}
}Please note that the above test drive class invokes only the showMetadata() method and does not call the preview() method.
However, the downloadFile() method is executed during the initialization of the CloudFile class, which is a computationally expensive operation.
Now that we know where the problem lies, lets think about what we would do in an actual project environment.
We would essentially have a database table storing the metadata of the files while the files would be stored in a storage bucket.

In our case the Database operation to get the file metadata is far less expensive than actually downloading the file to get metadata from the storage bucket – but that is what happens, the moment CloudFile class is initialized.
So, our aim is to provide the TestDriveCloudFile with something that does not call the downloadFile() method – as long as it is not absolutely necessary, hence saving our compute resources as well as keeping our application fast and responsive.
In order to do this, let us start by creating an interface that provides us with the following methods:
- showMetadata()
- preview()
public interface CloudFile {
void showMetadata();
File preview();
}Our RealCloudFile class is a concrete implementation of the above interface,
public class RealCloudFile implements CloudFile {...}But how does this solve our problem?? We still have to call the expensive downloadFile() method – even though we might just need to show the metadata 😶.
In that case, lets create another concrete class for CloudFile interface.
public class CloudFileProxy implements CloudFile {
private final String fileName;
private volatile RealCloudFile realFile;
private FileMetadata metadata;
public CloudFileProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void showMetadata() {
if (realFile == null) {
// For the sake of simplicity in this example
// Let us suppose we can get the file metadata from our database
// Using a method findByFileName
if (metadata == null) {
// Lazy-load metadata from DB if not already loaded
metadata = findByFileName(fileName);
}
System.out.println("File: " + metadata.getFileName());
System.out.println("Size: " + metadata.getSize());
System.out.println("Last Modified: " + metadata.getLastModified());
} else {
realFile.showMetadata();
}
}
@Override
public File preview() {
if (realFile == null) {
initialize(); // Double-checked locking for thread safety
}
return realFile.preview();
}
private synchronized void initialize() {
if (realFile == null) {
realFile = new RealCloudFile(fileName);
}
}
}Note:
- In case you are interested in understanding how to actually connect to Databases and retrieve required data please refer my JDBC blog post.
- In case this is the first time you have come across double-checked locking please refer my singleton blog post.
All right…so what we have done is we now a class called CloudFileProxy which performs a lightweight database call when only file metadata is required, avoiding the overhead of downloading the file.
The actual file download is deferred until it is truly necessary – specifically, when the preview() method is invoked.
That makes sense…🤔, but what if the client still calls the RealCloudFile class ??
Well here’s the neat trick 🪄, what if the client has no idea about the existence of RealCloudFile class.
What if to them, all that exists is the CloudFileProxy ??
How?? 😵
One common way to do this is to provide a factory that instantiates and returns the subject.
Because this happens in a factory method, we can wrap the subject with a proxy before returning it. Hence, the client never knows or cares that it’s using a proxy instead of a real thing 😉.
So now your client code will look like:
public class TestDriveCloudFile {
public static void main(String[] args) {
CloudFile file =
new CloudFileProxy("Proxy_Pattern.txt");
System.out.println("User browsing folder...");
file.showMetadata(); // DB call only
System.out.println("User clicks Preview...");
file.preview(); // Heavy download happens here
System.out.println("Metadata after download:");
file.showMetadata(); // Now uses real file metadata
}
}
Hence, you have created something that sits in front of your RealCloudFile class and forwarding requests to it, pretty much like the proxies in the internet context.
And there you have it 🥁… your first implementation of what we call Virtual Proxy Pattern.
Virtual Proxy
A virtual proxy acts as a representative for an object that may be expensive to create. The virtual proxy often defers creation of the object until necessary. It also acts as a surrogate for the object before and while it is being created. After that the proxy delegates requests directly to the real subject (for our example RealCloudFile).

Now that we understand that we use proxy pattern to create a representative object which controls access to another object, which may be remote, expensive to create or in need of securing, let’s now check out how the class diagram for proxy design pattern will look in most cases.

Now, that we have an in-depth understanding of virtual proxy, it is important to understand that there exists many different flavors of Proxy Pattern depending on the intent, or the problem they are trying to solve.
Some of the famous mutations of Proxy Pattern are:

Now that we have developed a solid understanding of the Proxy Pattern, let us also explore the scenarios in which it proves especially valuable and effective:
- Expensive Object Creation: When instantiating an object is resource-intensive, a proxy can defer its creation until it is actually needed, thereby optimizing performance and conserving system resources.
- Access Control and Security: When certain operations require permission checks or controlled access, a proxy can enforce security rules before delegating the request to the underlying object.
- Remote Object Interaction: When working with remote services or distributed systems where objects are costly or slow to retrieve, a proxy can manage communication efficiently and abstract the complexity of remote calls.
- Lazy Loading for Performance Optimization: When you want to load data or initialize resources only on demand, a proxy enables lazy loading, improving overall responsiveness and reducing unnecessary overhead.
And with that, we’ve wrapped up the Proxy Design Pattern – you’re now fully equipped with everything you need to know on Proxy 🥳.
.