← Back to list

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…

Ahmed Safwat · 2024-11-26 19:46 · 79 claps · 4.0 min read
#spring-boot #proxy #cglib #jdk
Open on Medium ↗

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:

  1. 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.

  2. Annotations Work via Proxies: • Features like @Transactional, @Cacheable, @Async, and @Retryable rely on proxies to work.

  3. Avoid Internal Calls: • Proxies can’t intercept internal calls. Move such methods to separate beans if they need to be intercepted.

  4. Public Methods Only: • Proxies can only intercept public methods. Private or protected methods won’t be proxied.

  5. 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

[embed]Proxying Mechanisms Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually…docs.spring.io

[embed]Java Dynamic proxy mechanism and how Spring is using it In this post, we will talk about Proxy pattern and how you can implement it using the JDK-provided-mechanism, Dynamic…medium.com


메타데이터
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