In modern microservice architectures, Redis is widely used for caching, distributed locking, session storage, and rate limiting. However, the way you interact with Redis differs significantly between a traditional Servlet‑based service (blocking) and a WebFlux‑based gateway (non‑blocking). This guide explains both approaches and provides ready‑to‑use configuration and code samples.
Understanding the Architectural Differences
Servlet containers follow a synchronous, blocking I/O model. Each request occupies a thread until all I/O operations complete, which can lead to thread‑pool exhaustion under high concurrency.
WebFlux, on the other hand, uses an asynchronous, non‑blocking I/O model. A single thread can handle many requests concurrently; when an I/O operation (like a Redis call) is in flight, the thread is freed to process other tasks. This makes WebFlux ideal for gateway scenarios that must handle tohusands of simultaneous connections.
- Using Redis in a Servlet‑Based Microservice
1.1 Dependencies (Maven)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
1.2 Application Configuration
spring:
redis:
host: localhost
port: 6379
password: yourpassword
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: 2000ms
1.3 Synchronous Redis Template
Spring Data Redis provides RedisTemplate and StringRedisTemplate for synchronous operations. Below is a utility class that performs common tasks:
@Component
public class ServletRedisHelper {
private final StringRedisTemplate template;
public ServletRedisHelper(StringRedisTemplate template) {
this.template = template;
}
public void storeString(String key, String value) {
template.opsForValue().set(key, value);
}
public void storeStringWithTtl(String key, String value, long ttlSeconds) {
template.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS);
}
public String fetchString(String key) {
return template.opsForValue().get(key);
}
public boolean deleteKey(String key) {
return Boolean.TRUE.equals(template.delete(key));
}
public boolean checkExists(String key) {
return Boolean.TRUE.equals(template.hasKey(key));
}
public boolean acquireLock(String lockKey, String ownerId, long ttlSeconds) {
return Boolean.TRUE.equals(
template.opsForValue().setIfAbsent(lockKey, ownerId, ttlSeconds, TimeUnit.SECONDS));
}
public boolean releaseLock(String lockKey, String ownerId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = template.execute(redisScript, Collections.singletonList(lockKey), ownerId);
return Long.valueOf(1).equals(result);
}
}
1.4 Caching with Annotations
Enable caching in a configuration class:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(15))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(cf)
.cacheDefaults(defaults)
.build();
}
}
Then use the annotations in your service:
@Service
public class OrderService {
@Cacheable(value = "orders", key = "#orderId")
public Order findOrderById(Long orderId) {
// database call
return orderRepository.findById(orderId).orElse(null);
}
@CachePut(value = "orders", key = "#order.id")
public Order updateOrder(Order order) {
orderRepository.save(order);
return order;
}
@CacheEvict(value = "orders", key = "#orderId")
public void removeOrder(Long orderId) {
orderRepository.deleteById(orderId);
}
}
- Using Redis in a WebFlux Gateway
Spring Boot provides the spring-boot-starter-data-redis-reactive starter for reactive Redis access.
2.1 Dependencies (Maven)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.2 Application Configuration
Configuration is identical to the Servlet case:
spring:
redis:
host: localhost
port: 6379
password: yourpassword
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
2.3 Reactive Redis Template
@Service
public class ReactiveRedisService {
private final ReactiveStringRedisTemplate reactiveTemplate;
public ReactiveRedisService(ReactiveStringRedisTemplate reactiveTemplate) {
this.reactiveTemplate = reactiveTemplate;
}
public Mono<Boolean> setValue(String key, String value) {
return reactiveTemplate.opsForValue().set(key, value);
}
public Mono<String> getValue(String key) {
return reactiveTemplate.opsForValue().get(key);
}
public Mono<Boolean> setValueWithTtl(String key, String value, Duration ttl) {
return reactiveTemplate.opsForValue().set(key, value, ttl);
}
public Mono<Long> removeKey(String key) {
return reactiveTemplate.delete(key);
}
}
2.4 Using Redis in a Gateway Filter
The following example shows a custom global filter that validates a JWT token stored in Redis:
@Component
public class JwtValidationFilter implements GlobalFilter, Ordered {
private final ReactiveRedisService redisService;
public JwtValidationFilter(ReactiveRedisService redisService) {
this.redisService = redisService;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
List<String> authHeader = exchange.getRequest().getHeaders().get("Authorization");
if (authHeader == null || authHeader.isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
String token = authHeader.get(0).replace("Bearer ", "");
return redisService.getValue("jwt:" + token)
.flatMap(userId -> {
if (userId != null) {
// attach user info to exchange attributes
exchange.getAttributes().put("userId", userId);
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
})
.switchIfEmpty(Mono.defer(() -> {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}));
}
@Override
public int getOrder() {
return -100;
}
}
- Key Differences Between the Two Approaches
| Aspect | Servlet (Blocking) | WebFlux (Non‑blocking) |
|---|---|---|
| Return types | Direct values (String, Object) |
Mono / Flux |
| Thread usage | Thread blocked during Redis call | Thread released, can handle other requests |
| Best suited for | Complex business logic, lower concurrency | High concurrency, I/O‑intensive gateways |
| Spring starter | spring-boot-starter-data-redis |
spring-boot-starter-data-redis-reactive |
- Conclusion
Both Servlet‑based services and WebFlux gateways can use Redis effectively, but the choice of client and programming model must align with the underlying I/O model. For traditional microservices, RedisTemplate offers a simple synchronous API. For reactive gateways, ReactiveRedisTemplate integrates seamlessly with the non‑blocking paradigm, enabling high throughput under heavy load.