Speeding Up Thymeleaf with Fragment Caching and Spring Cache
Learn how to cache Thymeleaf fragments with Spring Cache, improve rendering speed, manage cache keys, and avoid security pitfalls. Step‑by‑step example and trade‑off discussion included.
11 Dec 2025, 03:34 UTC

Problem: Re‑rendering the Same Fragment on Every Request
In a Spring MVC application, a page often contains a header, navigation menu, or dashboard widget that is identical for most users. Each request forces Thymeleaf to parse and render that fragment, even if the content hasn’t changed. The result is wasted CPU time and slower response times.
Thymeleaf’s Fragment Caching – The Core Idea
Thymeleaf can delegate the rendering of a fragment to Spring Cache. When a fragment is marked with @Cacheable, the first request renders it normally and stores the output in the cache. Subsequent requests fetch the stored HTML string, bypassing parsing and template logic.
Key points:
- Cache is server‑side; it does not replace HTTP caching for static assets.
- Only the fragment’s output is cached – the rest of the page is still rendered per request.
- Security expressions inside a cached fragment are evaluated per request; the cached HTML does not contain user‑specific data unless the cache key includes the user context.
Integrating with Spring Cache
Spring Cache is a thin abstraction over providers like Ehcache or Caffeine. To enable fragment caching, you need three things:
- Enable caching in your Spring Boot application with
@EnableCachingorspring.autoconfigure.cache.enabled=true. - Define a
CacheManagerbean (Spring Boot auto‑configures one if a provider is on the classpath). - Annotate the fragment method with
@Cacheableand specify a key expression that captures all model attributes that influence the fragment.
Example configuration:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// Caffeine with 5‑minute TTL
CaffeineCacheManager manager = new CaffeineCacheManager("headerFragment");
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES));
return manager;
}
}
Worked Example: A Counter Fragment
Suppose we have a fragment that displays how many times the page has been visited during the current session. The counter is expensive because it queries a database each time.
1. Service that increments the counter:
@Service
public class CounterService {
private final AtomicLong counter = new AtomicLong();
public long next() {
return counter.incrementAndGet();
}
}
2. Controller that adds the counter value to the model:
@Controller
public class PageController {
@Autowired CounterService counter;
@GetMapping("/dashboard")
public String dashboard(Model model) {
model.addAttribute("counter", counter.next());
return "dashboard";
}
}
3. Thymeleaf fragment with caching:
<div th:fragment="counterFragment(counter)" th:utext="${counter}"></div>
4. Enable caching for the fragment:
@Component
public class FragmentCache {
@Cacheable(value = "headerFragment", key = "#counter")
public String cacheCounterFragment(Long counter) {
// The fragment is rendered by Thymeleaf; this method simply returns the value.
return counter.toString();
}
}
How it works:
- The first request renders the fragment, stores the string “1” in the cache with key “1”.
- Subsequent requests see the same key and fetch “1” from the cache, skipping the
counter.next()call. - When the counter changes (e.g., after a cache eviction or TTL expiry), the fragment is re‑rendered.
Verification steps:
- Enable
logging.level.org.springframework.cache=DEBUGto see cache hits/misses. - Make two requests to
/dashboardand observe that the counter value is the same and that the second request logs a cache hit. - Delete the cache entry or wait for the TTL to expire, then request again. The counter should increment, indicating a cache miss.
Key Design & Security Trade‑offs
Choosing the cache key is critical. A coarse key like "header" will serve stale data when the underlying model changes. A fine‑grained key can be built from multiple model attributes:
@Cacheable(value = "headerFragment", key = "T(java.util.Objects).hash(#user.id, #theme)")
Security considerations:
- Do not cache fragments that contain user‑specific information unless the key includes the user ID or role.
- Spring Security expressions inside a cached fragment are re‑evaluated on each request, so the cached HTML remains safe for public parts.
- If a fragment mixes public and private data, split it into two fragments or guard the private part with
sec:authorizeand exclude it from caching.
Memory footprint: Large fragments can consume significant cache space. Measure fragment size and adjust TTL or eviction policy accordingly.
Actionable Checklist for Your Project
- Enable Spring Cache and pick a provider that fits your scale.
- Identify reusable fragments that are expensive to render.
- Annotate fragment methods with
@Cacheableand craft a key that includes all dynamic model attributes. - Set an appropriate TTL based on how often the fragment changes.
- Log cache activity during development to confirm hits/misses.
- Audit cached fragments for user‑specific data and adjust keys or exclude them.
- Combine fragment caching with HTTP cache headers for static assets.
Conclusion
Thymeleaf’s fragment caching, powered by Spring Cache, is a lightweight way to cut rendering time without touching controller logic. With careful key design and security awareness, you can cache large portions of a page, keep the cache fresh, and avoid leaking data between users.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.