📘 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
- Always code to interface — makes it easy to inject either proxy or real class
- Use
@Qualifier
to avoid bean conflicts - Keep proxies focused — don’t mix multiple responsibilities
- Make proxy testable — unit test behavior independently
- 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
Post a Comment
Leave Comment