java
43 lines · 7 steps
How Spring cache annotations keep data fresh
A Spring service uses @Cacheable and @CacheEvict to serve reads from cache while invalidating stale entries on writes.
Explained by
highlit
1@Service
2public class ProductCatalogService {
3
4 private final ProductRepository productRepository;
5 private final PricingClient pricingClient;
6
7 public ProductCatalogService(ProductRepository productRepository, PricingClient pricingClient) {
8 this.productRepository = productRepository;
9 this.pricingClient = pricingClient;
10 }
11
12 @Cacheable(cacheNames = "products", key = "#productId")
13 public ProductView getProduct(long productId) {
14 Product product = productRepository.findById(productId)
15 .orElseThrow(() -> new ProductNotFoundException(productId));
16 Money price = pricingClient.resolveCurrentPrice(product.getSku());
17 return ProductView.from(product, price);
18 }
19
20 @Cacheable(cacheNames = "productsByCategory", key = "#category", unless = "#result.isEmpty()")
21 public List<ProductView> getProductsByCategory(String category) {
22 return productRepository.findByCategory(category).stream()
23 .map(p -> ProductView.from(p, pricingClient.resolveCurrentPrice(p.getSku())))
24 .toList();
25 }
26
27 @Caching(evict = {
28 @CacheEvict(cacheNames = "products", key = "#result.id"),
29 @CacheEvict(cacheNames = "productsByCategory", key = "#command.category")
30 })
31 @Transactional
32 public Product updateProduct(UpdateProductCommand command) {
33 Product product = productRepository.findById(command.productId())
34 .orElseThrow(() -> new ProductNotFoundException(command.productId()));
35 product.rename(command.name());
36 product.recategorize(command.category());
37 return productRepository.save(product);
38 }
39
40 @CacheEvict(cacheNames = {"products", "productsByCategory"}, allEntries = true)
41 public void invalidateAll() {
42 }
43}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Cache reads with @Cacheable and evict on writes so callers never see stale product data.
- 2SpEL expressions like key and unless let you shape cache keys and skip caching empty results.
- 3Grouping multiple @CacheEvict under @Caching keeps two related caches consistent from a single write.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-spring-cache-annotations-keep-data-fresh-explained-java-5a36/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.