Skip to content
Development
Skill

/spring-data-redis

Use when implementing caching, session storage, rate limiting, or any Redis integration. Covers cache-aside pattern, key naming, TTL strategy, and serialization config.

From plugin
spring-boot-skills
33733 skills

Claude Code

$ npx -y skills add rrezartprebreza/spring-boot-skills --skill spring-data-redis --agent claude-code

SKILL.md

spring-data-redis.SKILL.md
name: spring-data-redis
description: >
  Use when implementing caching, session storage, rate limiting, or any Redis integration.
  Covers cache-aside pattern, key naming, TTL strategy, and serialization config.

Spring Data Redis

Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

Configuration

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(jsonSerializer()); // JSON, not Java serialization
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jsonSerializer());
        return template;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer()))
            .disableCachingNullValues();

        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .transactionAware()   // put/evict only after the surrounding transaction commits
            .withCacheConfiguration("orders", config.entryTtl(Duration.ofMinutes(5)))
            .withCacheConfiguration("products", config.entryTtl(Duration.ofHours(1)))
            .build();
    }

    // Records are final, so DefaultTyping.NON_FINAL writes no type id for them and every
    // cache read fails ("missing type id property '@class'"). The builder's defaultTyping(true)
    // types user classes too; the validator limits type ids to an allow-list instead of
    // LaissezFaire's "any class". BigDecimal is non-final, so java.math must be allowed.
    private GenericJackson2JsonRedisSerializer jsonSerializer() {
        ObjectMapper mapper = JsonMapper.builder()
            .addModule(new JavaTimeModule())
            .polymorphicTypeValidator(BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("com.example.")   // your DTO packages
                .allowIfSubType("java.util.")
                .allowIfSubType("java.math.")
                .allowIfSubType("java.time.")
                .build())
            .build();
        return GenericJackson2JsonRedisSerializer.builder()
            .objectMapper(mapper)
            .defaultTyping(true)
            .build();
    }
}

Key Naming Convention

{app}:{domain}:{id}          → orders:order:uuid-here
{app}:{domain}:list:{filter} → orders:order:list:status:PENDING
{app}:session:{userId}       → orders:session:uuid-here
{app}:ratelimit:{ip}         → orders:ratelimit:192.168.1.1

@Cacheable — Declarative Caching

@Service
@RequiredArgsConstructor
public class ProductService {

    @Cacheable(value = "products", key = "#id")
    public ProductResponse findById(UUID id) {
        return productRepository.findById(id)
            .map(ProductResponse::from)
            .orElseThrow(() -> new EntityNotFoundException("Product not found: " + id));
    }

    @CachePut(value = "products", key = "#result.id")  // update cache after write
    @Transactional
    public ProductResponse update(UUID id, UpdateProductRequest request) {
        Product product = productRepository.findById(id).orElseThrow();
        product.update(request);
        return ProductResponse.from(productRepository.save(product));
    }

    @CacheEvict(value = "products", key = "#id")  // invalidate on delete
    @Transactional
    public void delete(UUID id) {
        productRepository.deleteById(id);
    }

    @CacheEvict(value = "products", allEntries = true)  // clear all
    public void clearCache() {}
}

Manual Cache-Aside Pattern

@Service
@RequiredArgsConstructor
public class OrderCacheService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ObjectMapper objectMapper;
    private static final Duration TTL = Duration.ofMinutes(5);

    public Optional<OrderResponse> get(UUID orderId) {
        String key = "orders:order:" + orderId;
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached == null) return Optional.empty();
        return Optional.of(objectMapper.convertValue(cached, OrderResponse.class));
    }

    public void put(OrderResponse order) {
        String key = "orders:order:" + order.id();
        redisTemplate.opsForValue().set(key, order, TTL);
    }

    public void evict(UUID orderId) {
        redisTemplate.delete("orders:order:" + orderId);
    }
}

Rate Limiting with Redis

Use [AtomicWindowLimiter](templates/AtomicWindowLimiter.java) with StringRedisTemplate. It runs a single-key Lua script: the first allowed request creates the counter and expiry in one SET; later requests increment only below the limit and do not extend the window. Separate INCR and EXPIRE calls can leave a permanent counter if the client fails between them.

This is a first-request-anchored fixed window, not a sliding window or token bucket; bursts across expiry are possible. The example restricts windows to 1ms through one day. Derive the bounded bucket from trusted tenant/principal and operation data, not arbitrary client keys. Keep quota/window settings consistent across callers, bound key cardinality and use a dedicated namespace. The v2 prefix avoids reusi

Read more
@rrezartprebreza
Ships withspring-boot-skills

Production-grade Claude Code and Codex skills for Spring Boot developers

Stars on GitHub

Momentum

stars in total
337
Star history of rrezartprebreza/spring-boot-skills on GitHub, a chart by star-history.com

Other skills on spring-boot-skills.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about spring-data-redis.

Which coding agents can use spring-data-redis?
spring-data-redis installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.