Proxy Pattern in a Spring Boot Project

📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.

🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.

▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube

▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube

In real-world Spring Boot projects, we often need to add functionality like logging, caching, or access control around a service — but without changing the original service code.

This is where the Proxy Pattern comes in.

In this article, you’ll learn:

  • What the Proxy Pattern is
  • When and why to use it in backend development
  • A complete real-world example: securing an API call with a proxy
  • Step-by-step Spring Boot implementation
  • Best practices and common pitfalls

Let’s walk through it together.


What Is the Proxy Pattern?

The Proxy Pattern is a structural design pattern where a proxy object controls access to another object. The proxy adds additional behavior before or after calling the real object.

Think of it like a gatekeeper that wraps the original service.

In code terms:

  • You have a service that does something (e.g. fetch data)
  • You wrap it in a proxy that checks if the user has permission
  • The proxy then calls the real service

You can also use proxies for logging, caching, lazy loading, etc.


Real-World Example: Securing Product Data with a Proxy

Scenario

You’re building a Spring Boot REST API. It has a service that returns product information. But not all users should access all products.

Instead of adding access checks directly in the service, we’ll create a proxy that handles the security check before passing the call to the actual service.

This way, the business logic stays clean — and access control is handled separately.

Let’s build this step-by-step.


✅ Step 1: Define a Common Interface

Start by defining a simple interface that both the real service and the proxy will implement.

public interface ProductService {
    String getProductDetails(String productId, String userRole);
}

✅ Step 2: Create the Real Service

This class contains the core logic to return product details.

import org.springframework.stereotype.Component;

@Component("realProductService")
public class ProductServiceImpl implements ProductService {

    @Override
    public String getProductDetails(String productId, String userRole) {
        // Simulate fetching from DB
        return "Product ID: " + productId + ", Name: Premium Coffee, Price: $25";
    }
}

Notice: no security checks are here. This class only does its main job.


✅ Step 3: Create the Proxy Class

Now let’s add a proxy that checks the user’s role before allowing access.

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("securedProductService")
public class ProductServiceProxy implements ProductService {

    private final ProductService productService;

    public ProductServiceProxy(@Qualifier("realProductService") ProductService productService) {
        this.productService = productService;
    }

    @Override
    public String getProductDetails(String productId, String userRole) {
        if (!"ADMIN".equalsIgnoreCase(userRole)) {
            throw new SecurityException("Access denied. Only ADMIN can view product details.");
        }

        return productService.getProductDetails(productId, userRole);
    }
}

This is the proxy — it adds security before delegating to the actual service.


✅ Step 4: Use the Proxy in a Manager or Controller

You now inject the proxy (securedProductService) instead of the real one.

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class ProductManager {

    private final ProductService productService;

    public ProductManager(@Qualifier("securedProductService") ProductService productService) {
        this.productService = productService;
    }

    public String fetchProduct(String productId, String userRole) {
        return productService.getProductDetails(productId, userRole);
    }
}

✅ Step 5: Expose a REST API

Create a controller to test this flow.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @Autowired
    private ProductManager productManager;

    @GetMapping("/{id}")
    public ResponseEntity<String> getProduct(@PathVariable String id,
                                             @RequestParam String role) {
        try {
            String result = productManager.fetchProduct(id, role);
            return ResponseEntity.ok(result);
        } catch (SecurityException e) {
            return ResponseEntity.status(403).body(e.getMessage());
        }
    }
}

Test It

Call the endpoint:

GET /api/products/123?role=ADMIN

✅ Output:

Product ID: 123, Name: Premium Coffee, Price: $25

Try as a non-admin:

GET /api/products/123?role=USER

❌ Output:

403 Access denied. Only ADMIN can view product details.

Your controller doesn’t contain access logic. The proxy takes care of it.


Why Use Proxy Pattern in Spring Boot?

Use Case How Proxy Helps
Access control Check permissions before calling services
Logging Log input/output for debugging
Caching Return data from cache before calling DB
Rate limiting Limit how often a method is called
Lazy loading Load heavy resources only when needed

✅ Proxy vs Decorator vs AOP

  • Proxy: Controls access (like a gatekeeper)
  • Decorator: Adds features (like logging, retry)
  • AOP: Handles cross-cutting concerns (with annotations and aspects)

You can use proxies manually (as shown) or rely on Spring AOP for advanced use cases.


🔁 Other Real-World Proxy Examples

Scenario Proxy Use
File download API Check token validity before serving file
Billing system Check subscription before allowing charge
External API calls Add retry logic and timeouts via proxy
Sensitive logs Mask personal data before returning response

✅ Best Practices

  1. Always code to interface — makes it easy to inject either proxy or real class
  2. Use @Qualifier to avoid bean conflicts
  3. Keep proxies focused — don’t mix multiple responsibilities
  4. Make proxy testable — unit test behavior independently
  5. Avoid deep proxy chains — too many layers can become hard to follow

⚠️ When Not to Use the Proxy Pattern

Avoid proxy pattern if:

  • You only need to call one simple method
  • The logic is already covered by Spring AOP or filters
  • You’re adding unnecessary layers that slow development

Use proxy when it gives real structure to your code — especially for cross-cutting concerns like security, caching, or logging.


Summary

Step What We Did
1 Created a common ProductService interface
2 Built the real service (ProductServiceImpl)
3 Created a proxy (ProductServiceProxy) that checks access
4 Used the proxy in a manager class
5 Exposed a controller to trigger the call

The Proxy Pattern helped us keep the business logic clean and added access control without modifying the original service.


Final Thoughts

The Proxy Pattern is a simple but powerful way to separate responsibilities in your Spring Boot project. Whether it’s for access control, logging, caching, or throttling — proxies help you wrap your logic without touching the core service.

It’s also easy to test, maintain, and extend later.

Start small: pick one use case (like access control or logging) and wrap it using a proxy. You’ll quickly see the benefits of keeping responsibilities isolated and your code clean.

Good backend design isn’t about writing more code — it’s about writing better organized code. Proxy is one way to get there.

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare