Implementing a Custom Request scope cache Annotation with AOP in Spring Boot


Caching in Spring Boot can go beyond traditional mechanisms like Redis or Guava. What if you could mark methods for request-level caching just by annotating them? Enter a custom @RequestScopedCache
annotation, powered by AOP and request-scoped beans.
The Idea
We want to annotate methods so that their results are cached for the duration of a single HTTP request. If the method is called again with the same arguments during the same request, the cached result is returned. I found it quite useful since we have usually a request going through multiple phases and still calling same methods which might be cpu intensive or even network intensive and we cannot hold it in global cache since they often have results based on request data which can potentially be keep coming unique.
Step 1: Create a @RequestScopedCache
Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScopedCache {
}
Step 2: Build a Request-Scoped Cache Holder
@Component
@RequestScope
public class RequestCacheHolder {
private final Map<String, Object> cache = new HashMap<>();
public Object get(String key) {
return cache.get(key);
}
public void put(String key, Object value) {
cache.put(key, value);
}
public boolean contains(String key) {
return cache.containsKey(key);
}
}
Step 3: Create an Aspect to Intercept Annotated Methods
@Aspect
@Component
public class RequestScopedCacheAspect {
private final RequestCacheHolder requestCacheHolder;
public RequestScopedCacheAspect(RequestCacheHolder requestCacheHolder) {
this.requestCacheHolder = requestCacheHolder;
}
@Around("@annotation(RequestScopedCache)")
public Object cacheAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
String key = generateKey(joinPoint);
if (requestCacheHolder.contains(key)) {
return requestCacheHolder.get(key);
}
Object result = joinPoint.proceed();
requestCacheHolder.put(key, result);
return result;
}
private String generateKey(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object[] args = joinPoint.getArgs();
return method.getName() + Arrays.toString(args);
}
}
Note: We intentionally avoid including class names in the cache key to prevent any potential security or exposure concerns.
Step 4: Apply the Annotation on applicable method
@Service
public class ProductService {
private final ProductRepository productRepository;
private final CustomizationApiClient customizationApiClient;
public ProductService(ProductRepository productRepository, CustomizationApiClient customizationApiClient) {
this.productRepository = productRepository;
this.customizationApiClient = customizationApiClient;
}
@RequestScopedCache
public Product getCustomizedProduct(String productId, String userPreference) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
// Add customization based on user preference using a third-party API
return customizationApiClient.applyCustomization(product, userPreference);
}
}
In this example, even if getCustomizedProduct
is called multiple times with the same parameters within a single request, the customization logic and database call will only run once.
Benefits
Clean and declarative caching
Efficient reuse within a request
Avoids redundant logic in services
Caveats
Works best for idempotent, deterministic methods
Limited to request scope, not suitable for session or global caching
Comparison with global in-memory cache
When to Use Which:
Use Request Scope Cache when some heavily-processed data based on request argument is needed multiple times within the same request but is too transient to justify global caching.
Use Global Cache when the same data benefits multiple users or requests, and freshness can be managed appropriately.
Conclusion
Creating a @RequestScopedCache
annotation in Spring Boot is a powerful pattern when you want easy-to-manage, low-overhead caching at the HTTP request level. Combined with AOP, it keeps your service logic clean while boosting performance where it matters most.
#SpringBoot #Java #Caching #RequestScope #AOP #SoftwareArchitecture #BackendDevelopment
Originally published on Medium
Subscribe to my newsletter
Read articles from Rahul K directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Rahul K
Rahul K
I write about what makes good software great — beyond the features. Exploring performance, accessibility, reliability, and more.