Architecting Thymeleaf Fragment Caching in Spring Boot 3.2 Applications
Learn how to cache Thymeleaf fragments in Spring Boot 3.2 using Spring's Cache abstraction, covering requirements, minimal design, trust boundaries, operational checks, and when to revisit the design.
16 Dec 2025, 03:55 UTC

High‑traffic Spring Boot applications often waste CPU cycles re‑rendering UI fragments that change rarely, such as navigation menus, footers, or product grids. Thymeleaf already caches the parsed template structure, but it still evaluates expressions and executes controller logic on every request. Fragment caching stores the final HTML output of a component, bypassing the template engine entirely and reducing latency.
Requirements
- Spring Boot 3.2 (or later) with Spring Cache abstraction enabled.
- A cache provider compatible with Spring Cache – e.g., Caffeine for a local cache or Redis for a distributed cache.
- The fragment to cache must be either public (same for all users) or the cache key must include any variables that affect its output (locale, role, etc.).
Minimal Suitable Design
The design does not require changes to Thymeleaf templates. Instead, annotate the controller method that returns the fragment view name with @Cacheable. Spring intercepts the call, returns a cached view name (or the rendered string if you configure a custom wrapper) and skips the TemplateEngine execution.
@Controller
public class NavigationController {
@GetMapping("/fragments/nav")
@Cacheable(value = "fragments", key = "#locale.languageTag + '-' + #principal?.username ?: 'anon'")
public String getNavFragment(Model model) {
// Expensive data fetch – runs only on cache miss
model.addAttribute("categories", menuService.getGlobalCategories());
return "fragments/nav-menu";
}
}
The method returns a view name (fragments/nav-menu). When the cache contains an entry for the key, Spring returns the previously rendered view name (or the cached HTML if you use a ResponseBodyAdvice to capture the output). The template engine is not invoked, saving CPU.
Trust and Data Boundaries
- Key scoping: Include every variable that influences the fragment (e.g., user role, locale, tenant ID) in the
keySpEL expression. Omitting a variable leads to cross‑pollution where one user sees another’s data. - Data masking: Never cache fragments containing PII unless the cache is encrypted or scoped to a session‑specific key.
- Boundary trust: Treat cached HTML as immutable static data. If the underlying data changes, the cache entry must be evicted immediately; otherwise stale content may be served.
Operational Checks
Verify that caching works as expected:
- Start the application with logging level
DEBUGfororg.springframework.cache. The first request to/fragments/navshould log a cachePut; subsequent requests with the same key should log a cacheGet(hit). - Expose Micrometer metrics via Actuator:
Then query# application.properties management.endpoints.web.exposure.include=metrics,healthGET /actuator/metrics/cache.getsandGET /actuator/metrics/cache.puts. A healthy hit ratio isgets / (gets + puts)close to 1 for static fragments. - To confirm invalidation, evict the entry manually (e.g., via
CacheManager.getCache("fragments").evict(key)) or change the underlying data and call a method annotated with@CacheEvict. The next request should log a cache miss and a fresh template render.
Failure Modes and Design Triggers
- Stale data: If the backing data updates but the cache entry is not evicted, users see outdated UI. Mitigation: pair data‑change events with
@CacheEvictor use a short TTL. - Key explosion: Including highly variable data (e.g., request‑ID) in the key creates many cache entries, reducing hit rate and increasing memory usage. Keep the key granularity aligned with actual variability.
- Distributed cache inconsistency: When using Redis, ensure all instances share the same eviction policy and TTL. A missed eviction on one node can serve stale fragments while others have refreshed.
- Memory pressure: Local caches like Caffeine can grow unbounded. Configure a maximum size or use
CacheSpecwithmaximumSizeandexpireAfterWrite.
When to Revisit the Design
Re‑evaluate fragment caching when:
- You start caching fragments that contain user‑specific data without extending the key.
- Observed cache hit rate drops below 30 % for a fragment that should be stable, indicating key volatility.
- Application scales to multiple nodes and you switch from a local cache to a distributed cache; verify eviction propagation.
- New UI components are introduced whose rendering cost is low; the overhead of caching may outweigh benefits.
In those cases, either refine the key expression, reduce TTL, or remove the @Cacheable annotation and rely on Thymeleaf’s template‑level caching.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.