redis-integration

Use when integrating Redis with Quarkus — caching with @CacheResult/@CacheInvalidate, distributed locks, pub/sub messaging, rate limiting, Redisson integration, and both imperative + reactive clients.

hieplp/skills2 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: redis-integration
description: Use when integrating Redis with Quarkus — caching with @CacheResult/@CacheInvalidate, distributed locks, pub/sub messaging, rate limiting, Redisson integration, and both imperative + reactive clients.
license: MIT
---

# Redis Integration with Quarkus

## Overview

Quarkus provides `quarkus-redis-client` for low-level Redis access (both imperative and reactive) and integrates with the `@CacheResult` / `@CacheInvalidate` annotations for declarative caching. Redisson offers advanced features: distributed locks, pub/sub, rate limiters, and cluster/sentinel support.

Reference: [Quarkus Redis Extension Guide](https://jorney.srbala.com/guides/redis-reference), [Redisson Quarkus Integration](https://redisson.pro/blog/advanced-redis-integration-with-quarkus.html), [Quarkus Cache Guide](https://quarkus.io/guides/cache).

## When to Use

- Caching expensive database or API calls.
- Distributed locks for coordinated operations across instances.
- Pub/sub for real-time messaging between services.
- Rate limiting with Redis-backed counters.
- Session storage for stateless services.

Don't use for:
- Local-only single-instance apps (use Caffeine `@CacheResult` instead).
- Persistent storage — Redis is not durable by default.

## Dependencies (Gradle)

```groovy
// build.gradle (infrastructure module)
dependencies {
    // Low-level Redis client (imperative + reactive)
    implementation 'io.quarkus:quarkus-redis-client'

    // Declarative caching
    implementation 'io.quarkus:quarkus-cache'

    // Redisson (advanced: locks, pub/sub, rate limiters)
    implementation 'org.redisson:redisson-quarkus-30:3.43.0'
}
```

## Configuration

```properties
# application.properties

# Redis connection
quarkus.redis.hosts=redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
quarkus.redis.password=${REDIS_PASSWORD:}
quarkus.redis.timeout=5s
quarkus.redis.max-pool-size=10

# Declarative caching
quarkus.cache.caffeine.my-cache.expire-after-write=5m
quarkus.cache.redis.my-redis-cache.expire-after-write=10m

# Redisson
quarkus.redisson.single-server-config.address=redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
quarkus.redisson.single-server-config.password=${REDIS_PASSWORD:}
quarkus.redisson.threads=16
quarkus.redisson.netty-threads=32
```

## Patterns

### 1. Declarative Caching (@CacheResult / @CacheInvalidate)

```java
// application/src/main/java/dev/hieplp/wraith/application/service/UserService.java
@ApplicationScoped
public class UserService implements UserUseCase {

    @CacheResult(cacheName = "user-cache")
    public UserDTO getUser(String userId) {
        // Cache hit → skip this method. Cache miss → execute and store result.
        return userRepository.findById(new UserId(userId))
            .map(UserMapper.INSTANCE::toDTO)
            .orElseThrow(() -> new UserNotFoundException(userId));
    }

    @CacheInvalidate(cacheName = "user-cache")
    public void updateUser(String userId, UpdateUserCommand cmd) {
        var user = userRepository.findById(new UserId(userId))
            .orElseThrow(() -> new UserNotFoundException(userId));
        user.updateProfile(cmd.displayName(), cmd.avatarUrl());
        userRepository.save(user);
    }

    @CacheResult(cacheName = "user-list", lockTimeout = 5000)
    public List<UserDTO> listUsers() {
        return userRepository.findAll().stream()
            .map(UserMapper.INSTANCE::toDTO)
            .toList();
    }
}
```

### 2. Redis Data Source (type-safe high-level API)

```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/RedisCacheAdapter.java
@ApplicationScoped
public class RedisCacheAdapter implements CachePort {

    @Inject
    RedisDataSource redisDataSource;

    public void set(String key, String value, Duration ttl) {
        redisDataSource.string(String.class)
            .set(key, value, new SetArgs().ex(ttl));
    }

    public Optional<String> get(String key) {
        var value = redisDataSource.string(String.class).get(key);
        return Optional.ofNullable(value);
    }

    public void delete(String key) {
        redisDataSource.key().del(key);
    }

    // Hash operations
    public void setHashField(String hash, String field, String value) {
        redisDataSource.hash(String.class).hset(hash, field, value);
    }

    public Map<String, String> getHash(String hash) {
        return redisDataSource.hash(String.class).hgetall(hash);
    }
}
```

### 3. Distributed Lock (Redisson)

```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/DistributedLockService.java
@ApplicationScoped
public class DistributedLockService {

    @Inject
    RedissonClient redisson;

    public <T> T withLock(String lockKey, Duration waitTime, Duration leaseTime, Supplier<T> action) {
        var lock = redisson.getLock(lockKey);
        try {
            if (lock.tryLock(waitTime.toSeconds(), leaseTime.toSeconds(), TimeUnit.SECONDS)) {
                try {
                    return action.get();
                } finally {
                    lock.unlock();
                }
            }
            throw new LockAcquisitionException("Could not acquire lock: " + lockKey);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockAcquisitionException("Interrupted while acquiring lock", e);
        }
    }

    // Usage: prevent duplicate order processing
    public void processOrder(String orderId) {
        withLock("order:" + orderId, Duration.ofSeconds(5), Duration.ofSeconds(30), () -> {
            // Critical section — only one instance runs this
            return orderProcessor.process(orderId);
        });
    }
}
```

### 4. Rate Limiter (Redisson)

```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/RateLimiterService.java
@ApplicationScoped
public class RateLimiterService {

    @Inject
    RedissonClient redisson;

    public boolean tryAcquire(String key, long rate, Duration window) {
        var limiter = redisson.getRateLimiter("rate:" + key);
        limiter.trySetRate(RateType.OVERALL, rate, window);
        return limiter.tryAcquire();
    }

    // Usage in a REST filter:
    // if (!rateLimiter.tryAcquire(userId, 100, Duration.ofMinutes(1))) {
    //     throw new RateLimitExceededException();
    // }
}
```

### 5. Pub/Sub (Redisson)

```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/EventPublisher.java
@ApplicationScoped
public class EventPublisher {

    @Inject
    RedissonClient redisson;

    public void publish(String topic, String message) {
        redisson.getTopic(topic).publish(message);
    }
}

// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/EventSubscriber.java
@ApplicationScoped
public class EventSubscriber {

    @Inject
    RedissonClient redisson;

    public void onStart(@Observes StartupEvent event) {
        redisson.getTopic("user-events")
            .addListener(String.class, (channel, msg) -> {
                log.info("Received event on {}: {}", channel, msg);
                // Process event
            });
    }
}
```

### 6. Low-Level Redis API (imperative + reactive)

```java
// Imperative
@Inject
RedisDataSource highLevelApi;       // Type-safe
@Inject
RedisAPI lowLevelClient;            // Raw commands

// Reactive (Mutiny)
@Inject
ReactiveRedisDataSource reactiveHighLevel;
@Inject
io.vertx.mutiny.redis.client.RedisAPI reactiveLowLevel;
```

## Hexagonal Architecture: Redis as an Output Adapter

```
domain/                    # No Redis dependency
  └── model/User.java

application/               # Port interface only
  └── port/output/CachePort.java
      interface CachePort {
          void set(String key, String value, Duration ttl);
          Optional<String> get(String key);
          void delete(String key);
      }

infrastructure/            # Redis adapter implements CachePort
  └── redis/RedisCacheAdapter.java
      @ApplicationScoped
      class RedisCacheAdapter implements CachePort { ... }

  └── redis/DistributedLockService.java
      @ApplicationScoped
      class DistributedLockService { ... }
```

## Common Pitfalls

1. **Cache inconsistency.** When updating data, invalidate the cache BEFORE or IMMEDIATELY AFTER the write. Use `@CacheInvalidate` on update methods, and `@CacheResult` on read methods.
2. **Distributed lock not released.** Always use try-finally to unlock. Redisson's `lock.unlock()` must be called exactly once per `lock()`.
3. **Redis timeout on cold start.** Dev Services needs a few seconds to pull the Redis image. Set `quarkus.redis.timeout=15s` in dev.
4. **Serialization issues with @CacheResult.** The default cache uses Java serialization. For JSON, configure `quarkus.cache.redis.value-type=json`.
5. **Redisson version mismatch.** `redisson-quarkus-30` for Quarkus 3.x, `redisson-quarkus-20` for Quarkus 2.x. Using wrong artifact = classpath conflicts.
6. **Rate limit key explosion.** Use TTL on rate limiter keys to prevent Redis memory bloat: `limiter.trySetRate(..., Duration.ofMinutes(1))` with `limiter.expire(Duration.ofHours(1))`.

## Verification Checklist

- [ ] Redis Dev Services starts in dev mode (`docker ps` shows redis container).
- [ ] `@CacheResult` methods return cached values on second call.
- [ ] `@CacheInvalidate` clears cache and next read hits the database.
- [ ] Distributed lock test: two concurrent threads, only one enters critical section.
- [ ] Rate limiter blocks requests above threshold.
- [ ] Pub/sub: publisher's message received by subscriber.
- [ ] Hexagonal: `CachePort` interface in `application/`, Redis adapter in `infrastructure/`.

More API Design skills

← All API Design skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY