Proxy Design Pattern in Java: Controlling Access to Objects Without Changing Their Behavior

Description / Meta Description

Learn the Proxy Design Pattern in Java with practical examples. Understand how Proxy controls access to objects, enables lazy loading, caching, security, remote calls, and how frameworks like Spring AOP, Hibernate, and microservices rely heavily on Proxy implementations.


Proxy Design Pattern in Java: Controlling Access to Objects Without Changing Their Behavior

In the previous article, we explored the Facade Pattern, which simplifies complex subsystems behind a clean interface.

Facade answers:

“How do I simplify complexity?”

Proxy answers a different question:

“How do I control access to an object?”

This pattern is everywhere in modern Java applications.

If you’ve worked with:

  • Spring AOP
  • Hibernate Lazy Loading
  • Security Interceptors
  • Remote Services
  • Caching Layers

then you’ve already used Proxy—whether you realized it or not.


The Problem: Direct Access Isn’t Always Ideal

Consider a simple document service.

public interface DocumentService {

    void viewDocument();
}

Implementation:

public class RealDocumentService
        implements DocumentService {

    @Override
    public void viewDocument() {

        System.out.println(
            "Displaying Document");
    }
}

Usage:

DocumentService service =
        new RealDocumentService();

service.viewDocument();

Looks fine.

But what happens if we need:

  • Authorization
  • Logging
  • Caching
  • Rate Limiting
  • Remote Access
  • Lazy Loading

Do we modify RealDocumentService?

That quickly violates the Single Responsibility Principle.


What We Really Want

Instead of:

Client
   │
   ▼
Real Service

We want:

Client
   │
   ▼
Proxy
   │
   ▼
Real Service

The proxy sits between client and target object.

It decides:

Allow?
Block?
Cache?
Log?
Load Later?

What is Proxy Pattern?

Proxy is a Structural Design Pattern that:

Provides a placeholder or surrogate for another object and controls access to it.

Think of a security guard.

Visitor
   │
   ▼
Security Guard
   │
   ▼
Building

Visitors don’t access the building directly.

The guard controls access.

The guard is the Proxy.


Architecture Diagram

Client
   │
   ▼

DocumentService
   ▲
   │

DocumentProxy
   │
   ▼

RealDocumentService

Notice:

The proxy implements the same interface.

The client often cannot distinguish between proxy and real object.


Step 1: Define Service Interface

public interface DocumentService {

    void viewDocument();
}

Step 2: Create Real Service

public class RealDocumentService
        implements DocumentService {

    @Override
    public void viewDocument() {

        System.out.println(
            "Displaying Document");
    }
}

Step 3: Create Proxy

public class DocumentProxy
        implements DocumentService {

    private RealDocumentService service =
            new RealDocumentService();

    @Override
    public void viewDocument() {

        System.out.println(
            "Checking Authorization");

        service.viewDocument();
    }
}

Step 4: Client Usage

DocumentService service =
        new DocumentProxy();

service.viewDocument();

Output:

Checking Authorization
Displaying Document

The proxy adds behavior without modifying the real service.


Why Proxy Works

Without Proxy:

Client
   │
   ▼
Real Service

With Proxy:

Client
   │
   ▼
Proxy
   │
   ▼
Real Service

The proxy becomes a controlled gateway.


Common Types of Proxy

GoF describes Proxy broadly, but in modern systems several variations have emerged.


1. Virtual Proxy (Lazy Loading)

One of the most important types.

Suppose loading a large image takes:

5 Seconds
500 MB Memory

Loading it immediately may be wasteful.

Instead:

Image Proxy
     │
     ▼
Load Only When Needed

Example:

public class ImageProxy
        implements Image {

    private RealImage image;

    @Override
    public void display() {

        if(image == null) {

            image =
                new RealImage();
        }

        image.display();
    }
}

Object creation becomes lazy.


Hibernate Lazy Loading

One of the most famous Proxy examples.

Suppose:

@Entity
public class Customer {

    @OneToMany(fetch = FetchType.LAZY)
    private List<Order> orders;
}

Query:

Customer customer =
        repository.findById(id);

Hibernate does NOT immediately load orders.

Instead:

Customer Proxy
      │
      ▼
Orders Not Loaded Yet

When:

customer.getOrders();

is called,

Hibernate loads the data.

This is Virtual Proxy.


2. Protection Proxy (Security)

Controls access based on permissions.

Example:

public class AdminProxy
        implements DocumentService {

    private DocumentService service;

    public AdminProxy(
            DocumentService service) {

        this.service = service;
    }

    @Override
    public void viewDocument() {

        if(isAdmin()) {

            service.viewDocument();
        }
        else {

            throw new SecurityException(
                "Access Denied");
        }
    }
}

Used heavily in:

  • Spring Security
  • API Gateways
  • Authorization Layers

3. Caching Proxy

Avoid repeated expensive operations.

Example:

Database Query
     ↓
Cache Result
     ↓
Return Cached Data

Example:

public class ProductProxy
        implements ProductService {

    private Map<Integer, Product>
            cache = new HashMap<>();
}

Benefits:

  • Reduced DB calls
  • Faster responses
  • Better scalability

4. Remote Proxy

Represents objects located elsewhere.

Example:

Application
      │
      ▼
Proxy
      │
      ▼
Remote Service

Client thinks it is calling:

userService.getUser();

Reality:

HTTP Call
Network
Remote Server

This concept appears in:

  • RMI
  • SOAP
  • REST Clients
  • gRPC

Spring AOP: Proxy Everywhere

One of the best real-world examples.

Suppose:

@Transactional
public void saveOrder() {

}

Looks simple.

But Spring actually creates:

OrderService Proxy
        │
        ▼
Real OrderService

Proxy adds:

  • Transaction Begin
  • Transaction Commit
  • Rollback
  • Exception Handling

Without modifying your code.


Spring Security Example

@PreAuthorize(
    "hasRole('ADMIN')")
public void deleteUser() {

}

Spring creates a proxy.

Execution:

Client
   │
   ▼
Security Proxy
   │
   ▼
Target Method

Authorization occurs before execution.


Caching Example

Spring Cache:

@Cacheable("users")
public User getUser(Long id) {

}

Behind the scenes:

Cache Proxy
      │
      ├── Cache Hit
      │       ↓
      │   Return Data
      │
      └── Cache Miss
              ↓
         Execute Method

Again, proxy behavior.


Proxy vs Decorator

This is a common interview question.

Both wrap objects.

Both implement the same interface.

Yet intentions differ.


Decorator

Goal:

Add Behavior

Example:

Email
   ↓
Logging
   ↓
Encryption

Proxy

Goal:

Control Access

Example:

Security
Caching
Lazy Loading
Remote Calls

Proxy vs Adapter


Adapter

Purpose:

Make Interfaces Compatible

Example:

pay()
     ↓
makePayment()

Proxy

Purpose:

Control Access

Same interface.

Different behavior.


Proxy vs Facade


Facade

Goal:

Hide Complexity

Proxy

Goal:

Control Access

Facade simplifies.

Proxy protects or optimizes.


Benefits of Proxy Pattern

1. Security

Centralized authorization.


2. Lazy Loading

Objects created only when required.


3. Performance Optimization

Caching reduces expensive operations.


4. Remote Access Transparency

Client doesn’t know service is remote.


5. Logging and Monitoring

Interception becomes easy.


Common Mistakes


Mistake 1: Business Logic in Proxy

Proxy should control access.

Not become the business service.


Mistake 2: Excessive Proxy Chains

Proxy
  ↓
Proxy
  ↓
Proxy
  ↓
Proxy

Debugging becomes difficult.


Mistake 3: Confusing Proxy with Decorator

Remember:

Decorator
→ Enhance

Proxy
→ Control

When Should You Use Proxy?

Use Proxy when:

✔ Security is required

✔ Lazy loading is beneficial

✔ Caching improves performance

✔ Remote services are involved

✔ Monitoring and interception are needed


Avoid Proxy when:

❌ Direct access is acceptable

❌ Simpler solutions exist

❌ Additional abstraction adds no value


Structural Patterns Covered So Far

PatternPurpose
AdapterTranslate interfaces
DecoratorAdd behavior
FacadeSimplify complexity
ProxyControl access

Think of them like this:

Adapter
→ Translate

Decorator
→ Enhance

Facade
→ Simplify

Proxy
→ Control

Final Thoughts

The Proxy Pattern solves an extremely common architectural challenge:

How do we add security, caching, lazy loading, remote communication, or monitoring without modifying existing business logic?

By placing a controlled layer between clients and real objects, Proxy enables:

  • Better security
  • Improved performance
  • Lazy initialization
  • Transparent remote access
  • Framework-level magic

This is why Proxy sits at the heart of:

  • Spring AOP
  • Spring Transactions
  • Spring Security
  • Hibernate Lazy Loading
  • Caching Frameworks
  • Microservice Clients

In the next article, we’ll continue our Structural Design Pattern journey with:

Composite Pattern — Treating Individual Objects and Groups of Objects Uniformly

You’ll learn how file systems, organization hierarchies, menu structures, and UI component trees rely heavily on Composite Pattern principles.

Leave a Reply

Your email address will not be published. Required fields are marked *