home / skills / giuseppe-trisciuoglio / developer-kit / spring-boot-cache

This skill helps you enable and manage Spring Cache across services, improving performance with @Cacheable, TTLs, and eviction strategies.

npx playbooks add skill giuseppe-trisciuoglio/developer-kit --skill spring-boot-cache

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
9.4 KB
---
name: spring-boot-cache
description: Provides patterns for enabling and operating the Spring Cache abstraction in Spring Boot. Use when implementing application-level caching for performance-sensitive workloads.
allowed-tools: Read, Write, Bash
category: backend
tags: [spring-boot, caching, performance, cacheable, cache-managers]
version: 1.1.0
---

# Spring Boot Cache Abstraction

## Overview

Spring Boot ships with a cache abstraction that wraps expensive service calls
behind annotation-driven caches. This abstraction supports multiple cache
providers (ConcurrentMap, Caffeine, Redis, Ehcache, JCache) without changing
business code. The skill provides a concise workflow for enabling caching,
managing cache lifecycles, and validating behavior in Spring Boot 3.5+ services.

## When to Use

- Add `@Cacheable`, `@CachePut`, or `@CacheEvict` to Spring Boot service methods.
- Configure Caffeine, Redis, or JCache cache managers for Spring Boot.
- Diagnose cache invalidation, eviction scheduling, or cache key issues.
- Expose cache management endpoints or scheduled eviction routines.

Use trigger phrases such as **"implement service caching"**, **"configure
CaffeineCacheManager"**, **"evict caches on update"**, or **"test Spring cache
behavior"** to load this skill.

## Instructions

Follow these steps to implement caching in Spring Boot applications:

### 1. Add Cache Dependencies

Include spring-boot-starter-cache and your preferred cache provider (Caffeine, Redis, Ehcache) in the project dependencies.

### 2. Enable Caching

Add @EnableCaching to a @Configuration class and declare CacheManager bean(s) for your cache providers.

### 3. Define Cache Configuration

Configure cache names, TTL settings, and capacity limits in application.yml or via CacheManager customizer beans.

### 4. Anotate Service Methods

Apply @Cacheable for read operations, @CachePut for updates, and @CacheEvict for invalidation. Use @CacheConfig to define default cache names at class level.

### 5. Configure Cache Keys and Conditions

Use SpEL expressions to define cache keys (key = "#user.id"), conditions (condition = "#price > 0"), and unless clauses (unless = "#result == null").

### 6. Implement Cache Eviction Strategy

Create scheduled jobs or use @CacheEvict with allEntries=true to periodically clear time-sensitive caches.

### 7. Monitor Cache Performance

Enable Actuator cache endpoint to observe hit/miss ratios. Consider Micrometer metrics for production observability.

## Prerequisites

- Java 17+ project based on Spring Boot 3.5.x (records encouraged for DTOs).
- Dependency `spring-boot-starter-cache`; add provider-specific starters as
  needed (`spring-boot-starter-data-redis`, `caffeine`, `ehcache`, etc.).
- Constructor-injected services that expose deterministic method signatures.
- Observability stack (Actuator, Micrometer) when operating caches in
  production.

## Quick Start

1. **Add dependencies**

   ```xml
   <!-- Maven -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-cache</artifactId>
   </dependency>
   <dependency> <!-- Optional: Caffeine -->
       <groupId>com.github.ben-manes.caffeine</groupId>
       <artifactId>caffeine</artifactId>
   </dependency>
   ```

   ```gradle
   implementation "org.springframework.boot:spring-boot-starter-cache"
   implementation "com.github.ben-manes.caffeine:caffeine"
   ```

2. **Enable caching**

   ```java
   @Configuration
   @EnableCaching
   class CacheConfig {
       @Bean
       CacheManager cacheManager() {
           return new CaffeineCacheManager("users", "orders");
       }
   }
   ```

3. **Annotate service methods**

   ```java
   @Service
   @CacheConfig(cacheNames = "users")
   class UserService {

       @Cacheable(key = "#id", unless = "#result == null")
       User findUser(Long id) { ... }

       @CachePut(key = "#user.id")
       User refreshUser(User user) { ... }

       @CacheEvict(key = "#id", beforeInvocation = false)
       void deleteUser(Long id) { ... }
   }
   ```

4. **Verify behavior**
   - Run focused unit tests that call cached methods twice and assert repository
     invocations.
   - Inspect Actuator `cache` endpoint (if enabled) for hit/miss counters.

## Implementation Workflow

### 1. Define Cache Strategy

- Map hot-path read operations to `@Cacheable`.
- Use `@CachePut` on write paths that must refresh cache entries.
- Apply `@CacheEvict` (`allEntries = true` when invalidating derived caches).
- Combine operations with `@Caching` to keep multi-cache updates consistent.

### 2. Shape Cache Keys and Conditions

- Generate deterministic keys via SpEL (e.g. `key = "#user.id"`).
- Guard caching with `condition = "#price > 0"` for selective caching.
- Prevent null or stale values with `unless = "#result == null"`.
- Synchronize concurrent updates via `sync = true` when needed.

### 3. Manage Providers and TTLs

- Configure provider-specific options:
  - Caffeine spec: `spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=10m`
  - Redis TTL: `spring.cache.redis.time-to-live=600000`
  - Ehcache XML: define `ttl` and heap/off-heap resources.
- Expose cache names via `spring.cache.cache-names=users,orders,catalog`.
- Avoid on-demand cache name creation in production unless metrics cover usage.

### 4. Operate and Observe Caches

- Surface cache maintenance via a dedicated `CacheManagementService` with
  programmatic `cacheManager.getCache(name)` access.
- Schedule periodic eviction for time-bound caches using `@Scheduled`.
- Wire Actuator `cache` endpoint and Micrometer meters to track hit ratio,
  eviction count, and size.

### 5. Test and Validate

- Prefer slice or unit tests with Mockito/SpyBean to ensure method invocation
  counts.
- Add integration tests with Testcontainers for Redis/Ehcache when using
  external providers.
- Validate concurrency behavior under load (e.g. `sync = true` scenarios).

## Advanced Options

- Integrate JCache annotations when interoperating with providers that favor
  JSR-107 (`@CacheResult`, `@CacheRemove`). Avoid mixing with Spring annotations
  on the same method.
- Cache reactive return types (`Mono`, `Flux`) or `CompletableFuture` values.
  Spring stores resolved values and resubscribes on hits; consider TTL alignment
  with publisher semantics.
- Apply HTTP caching headers using `CacheControl` when exposing cached responses
  via REST.

## Examples

### Example 1: Basic @Cacheable Usage

**Input:**
```java
// First call - cache miss, method executes
User user1 = userService.findUser(1L);

// Second call - cache hit, method skipped
User user2 = userService.findUser(1L);
```

**Output:**
```
// First call logs:
Hibernate: select u1_0.id,u1_0.name from users u1_0 where u1_0.id=?

// Second call: No SQL executed (served from cache)
```

### Example 2: Conditional Caching with SpEL

**Input:**
```java
@Cacheable(value = "products", key = "#id", condition = "#price > 100")
public Product getProduct(Long id, BigDecimal price) {
    return productRepository.findById(id);
}

// Only expensive products are cached
getProduct(1L, new BigDecimal("50.00"));   // Not cached
getProduct(2L, new BigDecimal("150.00"));  // Cached
```

**Output:**
```
Cache 'products' contents after operations:
{2=Product(id=2, price=150.00)}
```

### Example 3: Cache Eviction

**Input:**
```java
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

deleteUser(1L);
```

**Output:**
```
Cache 'users' entry for key '1' removed
```

---

- Load [`references/cache-examples.md`](references/cache-examples.md) for
  progressive scenarios (basic product cache, conditional caching, multilevel
  eviction, Redis integration).
- Load [`references/cache-core-reference.md`](references/cache-core-reference.md)
  for annotation matrices, configuration tables, and property samples.

## References

- [`references/spring-framework-cache-docs.md`](references/spring-framework-cache-docs.md):
  curated excerpts from the Spring Framework Reference Guide (official).
- [`references/spring-cache-doc-snippet.md`](references/spring-cache-doc-snippet.md):
  narrative overview extracted from Spring documentation.
- [`references/cache-core-reference.md`](references/cache-core-reference.md):
  annotation parameters, dependency matrices, property catalogs.
- [`references/cache-examples.md`](references/cache-examples.md):
  end-to-end examples with tests.

## Best Practices

- Prefer constructor injection and immutable DTOs for cache entries.
- Separate cache names per aggregate (`users`, `orders`) to simplify eviction.
- Log cache hits/misses only at debug to avoid noise; push metrics via Micrometer.
- Tune TTLs based on data staleness tolerance; document rationale in code.
- Guard caches that store PII or credentials with encryption or avoid caching.
- Align cache eviction with transactional boundaries to prevent dirty reads.

## Constraints and Warnings

- Avoid caching mutable entities that depend on open persistence contexts.
- Do not mix Spring cache annotations with JCache annotations on the same
  method.
- Ensure multi-level caches (e.g. Caffeine + Redis) maintain consistency; prefer
  publish/subscribe invalidation channels.
- Validate serialization compatibility when caching across service instances.
- Monitor memory footprint to prevent OOM when using in-memory stores.

## Related Skills

- [`../spring-boot-rest-api-standards`](../spring-boot-rest-api-standards/SKILL.md)
- [`../spring-boot-test-patterns`](../spring-boot-test-patterns/SKILL.md)
- [`../unit-test-caching`](../unit-test-caching/SKILL.md)

Overview

This skill provides a concise, practical workflow for enabling and operating the Spring Cache abstraction in Spring Boot services. It covers adding dependencies, enabling caching, configuring cache managers (Caffeine, Redis, Ehcache), annotating service methods, and validating behavior with tests and observability. The guidance targets Spring Boot 3.5+ projects and focuses on reliable, production-safe caching patterns.

How this skill works

The skill inspects service method boundaries and recommends using @Cacheable for read paths, @CachePut to refresh entries on writes, and @CacheEvict for invalidation. It explains how to declare CacheManager beans, configure cache names and TTLs via properties or customizers, and shape cache keys and conditions with SpEL. It also covers operational concerns: scheduled eviction, Actuator cache endpoints, and Micrometer metrics for hit/miss monitoring.

When to use it

  • Implementing application-level caching for performance-sensitive read paths.
  • Configuring provider-specific options (Caffeine, Redis, JCache) and TTLs.
  • Diagnosing cache key collisions, stale data, or eviction scheduling issues.
  • Exposing cache management or scheduled eviction routines for time-sensitive data.
  • Validating caching behavior with unit/integration tests and observability stacks.

Best practices

  • Prefer constructor injection and immutable DTOs for cached values to avoid mutable state.
  • Separate cache names per aggregate (e.g., users, orders) to simplify eviction and metrics.
  • Use deterministic SpEL keys and guard caching with condition/unless to prevent caching nulls or undesired entries.
  • Tune TTLs based on staleness tolerance and document the rationale near configuration.
  • Push cache metrics via Micrometer and keep hit/miss logs at debug level to avoid noise.

Example use cases

  • Cache user lookups with Caffeine for low-latency hot-path reads and refresh with @CachePut on updates.
  • Use Redis as a shared cache with TTLs and Testcontainers-based integration tests for CI validation.
  • Apply conditional caching for expensive queries (e.g., only cache products priced above a threshold).
  • Schedule periodic @CacheEvict allEntries=true for time-bound caches like price or inventory snapshots.
  • Expose Actuator cache endpoints and Micrometer meters to monitor hit ratio and eviction counts in production.

FAQ

Can I mix Spring cache annotations and JCache annotations?

Avoid mixing both on the same method; prefer one annotation style to prevent unpredictable behavior.

How do I prevent caching null results or stale entries?

Use unless = "#result == null" to skip nulls and tune TTLs or scheduled eviction for staleness; consider sync = true for concurrent refreshes.