The Proxy pattern puts an object between a caller and the object that performs the real work. Both expose the same contract, so the caller can use the proxy in the same way. The proxy controls when or whether the request reaches the real object.
Use Proxy when access needs a policy, such as delaying an expensive load until the first request. Our example reads a report file only when the caller asks for its contents. Creating the proxy does not read the file.
1. How the Proxy Pattern Works
ReportDocument is the shared interface. FileReport performs the actual file read. LazyReport also implements the interface and creates a FileReport only when read() is called for the first time.
This is a virtual proxy, a proxy that delays creating or loading something. Other proxy policies can check access rights, represent a remote service or coordinate access. The pattern describes the arrangement, not a fixed list of features every proxy must provide.
The three roles have distinct jobs.
| Participant | Responsibility |
|---|---|
| Subject | ReportDocument is the interface used by callers. |
| Proxy | LazyReport delays creating the real report. |
| Real subject | FileReport reads and retains the report text. |
The caller reads through the shared interface. On the first read, the proxy creates the real report; later reads reuse it.
2. Define the Shared Interface and Real Report
The interface describes the operation the caller needs. Reading may fail, so it declares IOException instead of hiding that failure.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
interface ReportDocument {
String read() throws IOException;
}
final class FileReport implements ReportDocument {
private final String content;
FileReport(Path path) throws IOException {
System.out.println("Loading the report file");
content = Files.readString(path);
}
public String read() {
return content;
}
}
FileReport reads and stores the content during construction. Its later read() calls return that stored string. In an application, the expensive work might instead be parsing a large document or contacting another service.
3. Delay Loading in the Proxy
LazyReport remembers the path without opening it. Its target field starts empty. The first read() creates the real report, and later reads reuse it.
final class LazyReport implements ReportDocument {
private final Path path;
private FileReport target;
LazyReport(Path path) {
this.path = path;
}
public String read() throws IOException {
if (target == null) {
target = new FileReport(path);
}
return target.read();
}
}
The caller does not need to call a separate loading method. It continues to use ReportDocument.read(). If loading fails, the exception reaches the caller and target remains unset, so a later call attempts the load again.
This implementation is intended for a single calling thread. Two threads could both observe an empty target and load separate reports. A shared proxy needs an appropriate synchronization policy.
4. Observe When the File Is Loaded
Place the imports and types above this entry point in ProxyDemo.java. The program creates a small temporary file so the example does not depend on a file already existing on the machine.
public class ProxyDemo {
public static void main(String[] args) throws IOException {
Path path = Files.createTempFile("report-", ".txt");
try {
Files.writeString(path, "Completed orders 12");
ReportDocument report = new LazyReport(path);
System.out.println("Proxy created");
System.out.println(report.read());
System.out.println(report.read());
} finally {
Files.deleteIfExists(path);
}
}
}
javac ProxyDemo.java
java ProxyDemo
Proxy created
Loading the report file
Completed orders 12
Completed orders 12
The loading message appears after proxy creation and only once. That shows the delayed load and reuse. It does not prove that this approach is faster for every workload.
The report content is a snapshot of the file at the first successful load. If the file changes, this proxy still returns the old content. An application that needs fresh data must define a reload or expiration policy. It must also consider how much file content it can safely keep in memory.
The Java file API documents the file operations used here.
5. Proxy Does Not Require Inheritance
Our proxy implements the same interface and holds the real object. It does not extend FileReport. Some frameworks generate subclasses, but inheritance is not a requirement of the pattern.
Java also provides dynamic proxies for interfaces. They route calls through an invocation handler. That mechanism can reduce handwritten forwarding code, but it does not choose the application’s access, caching or error policy for us.
6. Proxy Compared With Decorator and Adapter
| Pattern | Main purpose |
|---|---|
| Proxy | Control access to another object |
| Decorator | Add behavior by wrapping an object |
| Adapter | Translate between interfaces that do not match |
Proxy and Decorator can look similar in code because both may forward calls. We distinguish them by the reason for the wrapper. Here, the wrapper controls when loading occurs.
Browse the complete Proxy example on GitHub to compile and run it locally.
7. Conclusion
Proxy keeps an access policy behind the same contract the caller already uses. The lazy report example makes the first read perform the load and lets later reads reuse its result. Loading failures, data freshness and concurrent access still need explicit decisions.
Hi there, i wanted your help to understand how can i use these GOF patterns to solve this problem
Suppose i have an Interface called “Adapter” which has a method Execute that takes an input and executes to return an output. I can have various implementations of this interface to simulate a Database Adapter, Webservice Adapter, REST Adapter, Messaging Adapter etc. All these implementations will have certain common independent characteristics – like Logging the input and output ( i don’t want to have them implemented in each implementation).
Also i have another Interface called “Controller” – this one has a method Execute . The responsibility of this component is to call 1..N Adapters defined above. Something like a orchestrator based on individual scenarios. But also need to have common functionality like security/access checks that must be executed automatically in this method.
Such a way, i can have a plugin Receiver (REST/WS/MQ) that receives the input and dynamically calls my Controller implementation which checks cross cutting concerns (security/access) and invokes my implementation of “Controller.Execute”. And in my implementation of Controller.execute i call the dynamic references of the Adapter implementations to invoke the Database/Webservice/REST etc to perform the transaction/operation.
Basically Receiver->Calling something 1-> My Controller-> Calling something 2-> 1..N Adapter implementations.
What sort of mix of patterns do you think i should use here? Any suggestions?
You may use a customized implementation of chain of responsibility.
@Sanjay Tiwari
I think maybe I can answer you question. Correct me if I’m wrong.
If you have a object need to delay its creation until real demand. You can init it in the proxy when it need to be used.
public class DelayInitObject { public DelayInitObject() { System.out.println("init the delay init object"); } } public class RealObjectImpl implements RealObject { DelayInitObject delayInitObject; @Override public void doSomething() { System.out.println("do something in real object impl"); } } public class RealObjectProxy extends RealObjectImpl { @Override public void doSomething() { System.out.println("delegate in the proxy and call the real method"); if (super.delayInitObject==null){ System.out.println("delayInitObject in real object is null, init one"); super.delayInitObject = new DelayInitObject(); } super.doSomething(); } public static void main(String[] args) { RealObjectProxy realObjectProxy = new RealObjectProxy(); realObjectProxy.doSomething(); realObjectProxy.doSomething(); } }How this pattern delaying object creation. As you have extended the real object class from proxy. Creation of proxy object will call super class constructor as well..