Spring Boot Proxies In Nutshell
When stepping into Spring Boot’s magical world, it’s easy to marvel at how annotations like @Transactional, @Cacheable, and @Async just…
Spring Boot Proxies In Nutshell

When stepping into Spring Boot’s magical world, it’s easy to marvel at how annotations like @Transactional, @Cacheable, and @Async just work. But what happens when they don’t? For Magda, a seasoned developer, this question turned into an exploration of the hidden mechanics behind Spring’s proxy system.
This is her story — and along the way, we’ll learn how Spring Boot’s proxies work, the differences between JDK dynamic and CGLIB proxies, and when to use each.
The Mystery of Ignored Annotations
One day, Magda was tasked with building a notification service for her team. She designed a NotificationService class to handle email notifications and wanted to cache some expensive email template generation logic. Here’s what her code looked like:
@Service
public class NotificationService {
@Cacheable("emailTemplates")
public String generateEmailTemplate(String type) {
// Simulate an expensive operation
return "Template for: " + type;
}
public void sendNotification(String email, String type) {
String template = generateEmailTemplate(type);
// Logic to send the notification
}
}
She annotated the generateEmailTemplate method with @Cacheable, expecting Spring to cache the result. But when she ran her tests, the caching didn’t work! Each call to generateEmailTemplate executed the method anew, ignoring the cache.
Magda was puzzled. Why wasn’t @Cacheable working as advertised?
Discovering the Power of Proxies
Determined to find the answer, Magda delved into Spring’s documentation. She learned that Spring enables features like @Cacheable, @Transactional, and @Async using proxies. Proxies are special objects that wrap around your class to intercept method calls and apply additional behavior, like caching or transaction management.
But Magda also learned an important detail: proxies can’t intercept internal method calls. In her example, the sendNotification method called generateEmailTemplate directly, bypasses the proxy. That’s why @Cacheable didn’t work.
The Solution: Use the Proxy
To fix this, Magda moved the generateEmailTemplate method into a separate service:
@Service
public class TemplateService {
@Cacheable("emailTemplates")
public String generateEmailTemplate(String type) {
// Simulate an expensive operation
return "Template for: " + type;
}
}
@Service
public class NotificationService {
private final TemplateService templateService;
public NotificationService(TemplateService templateService) {
this.templateService = templateService;
}
public void sendNotification(String email, String type) {
String template = templateService.generateEmailTemplate(type);
// Logic to send the notification
}
}
Now, the call to generateEmailTemplate went through the proxy, and the caching worked as expected. Sarah had unlocked the first secret of Spring proxies.
JDK Proxies vs. CGLIB Proxies
Magda’s journey didn’t stop there. She soon discovered that Spring uses two types of proxies: JDK dynamic proxies and CGLIB proxies. The choice between them could affect how her code behaved.
JDK Dynamic Proxies
• JDK proxies work by implementing the same interface as the target class and delegating method calls. • If a class implements an interface, Spring defaults to using JDK proxies.
CGLIB Proxies
• CGLIB proxies subclass the target class and override its methods to apply additional behavior. • Spring uses CGLIB if the class doesn’t implement any interfaces or if explicitly configured.
Choosing the Right Proxy
Magda realized she could control the type of proxy Spring used. For example, she could force Spring to use CGLIB proxies even for interface-based classes:
@EnableTransactionManagement(proxyTargetClass = true)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
// Configuration details
}
But which proxy was better? Here’s what she learned:

Magda decided to stick with JDK proxies unless she needed CGLIB’s ability to proxy concrete classes.
Exploring More Annotations
As Magda gained confidence, she started using more Spring annotations, uncovering new scenarios where proxies played a crucial role.
Example 1: @Async
Magda wanted to send notifications asynchronously. She annotated her method with @Async:
@Service
public class NotificationService {
@Async
public void sendNotification(String email, String type) {
// Logic to send notification
}
}
However, she noticed the method wasn’t running asynchronously when called directly from another method in the same class. Once again, the internal call bypassed the proxy.
The fix was the same: move the asynchronous method to a separate service.
Example 2: @Retryable
Later, Magda used Spring’s @Retryable annotation to automatically retry failed email delivery attempts. The proxy mechanism allowed Spring to wrap the method and handle retries transparently:
@Service
public class EmailService {
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000))
public void sendEmail(String email, String content) {
// Logic to send email
}
}
Here, Magda didn’t encounter issues because @Retryable worked seamlessly as long as the method was called through the proxy.
Golden Rules of Spring Proxies
Magda compiled her learnings into these golden rules:
-
Understand the Proxy Type: • Spring defaults to JDK proxies for interface-based classes. • Use CGLIB proxies for concrete classes or if annotations are applied to the implementation class.
-
Annotations Work via Proxies: • Features like @Transactional, @Cacheable, @Async, and @Retryable rely on proxies to work.
-
Avoid Internal Calls: • Proxies can’t intercept internal calls. Move such methods to separate beans if they need to be intercepted.
-
Public Methods Only: • Proxies can only intercept public methods. Private or protected methods won’t be proxied.
-
Explicit Configuration: • Use proxyTargetClass = true in configuration to enforce CGLIB proxies when needed.
Conclusion
Spring Boot’s proxy mechanism is a powerful tool, but it comes with quirks. By understanding how JDK and CGLIB proxies work, Magda turned her frustrations into mastery. Whether you’re caching expensive operations, managing transactions, or sending asynchronous notifications, knowing when and how proxies come into play can save you hours of debugging.
Resources
메타데이터
- post_id
- 07e77d0b67a4
- slug
- spring-boot-proxies-in-nutshell-07e77d0b67a4
- url
- https://medium.com/@ahmed.abdelfaheem/spring-boot-proxies-in-nutshell-07e77d0b67a4
- canonical_url
- https://medium.com/@ahmed.abdelfaheem/spring-boot-proxies-in-nutshell-07e77d0b67a4
- author_url
- https://medium.com/@ahmed.abdelfaheem
- status
- ok
- fetched_at
- 2026-07-22 00:18:19