This guide demonstrates hands-on Redis usage in Java applications, covering direct client interaction via Jedis, configuration best practices, and seamless integration with Spring Boot using both Jedis and Lettuce clients.
Command-Line Interaction
Before integrating into code, verify Redis connectivity using the CLI:
redis-cli -h 192.168.100.110 -p 6379 -a 123456
> SET user:1001 '{"name":"Alice","role":"admin"}'
> GET user:1001
Standalone Jedis Setup
Add the Jedis dependency with test scope for lightweight development use:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.3</version>
<scope>test</scope>
</dependency>
Implement a thread-safe connection pool with modern defaults:
public class RedisConnectionPool {
private static volatile JedisPool instance;
public static JedisPool getInstance() {
if (instance == null) {
synchronized (RedisConnectionPool.class) {
if (instance == null) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(16);
config.setMaxIdle(12);
config.setMinIdle(4);
config.setMinEvictableIdleTimeMillis(60_000);
config.setTestOnBorrow(true);
instance = new JedisPool(config, "192.168.100.110", 6379, 2000, "123456");
}
}
}
return instance;
}
public static void closeQuietly(Jedis jedis) {
if (jedis != null) jedis.close();
}
}
Core Redis Data Structures
- String: Binary-safe key-value storage supporting counters, caching, and session data.
- Hash: Efficinet object mapping (e.g.,
HSET user:1001 name "Bob" age "32") with field-level operations. - List: Insertion-ordered collections ideal for queues, timelines, or recent activity feeds.
- Set: Unordered, unique element collections supporting union/intersection/difference operations.
- Sorted Set (ZSet): Scored elements enabling leaderboards, priority queues, and range-based queries.
Example ZSet usage:
@Test
void populateLeaderboard() {
try (Jedis jedis = RedisConnectionPool.getInstance().getResource()) {
Map<String, Double> scores = Map.of(
"player:alpha", 2450.0,
"player:bravo", 3120.0,
"player:charlie", 1980.0,
"player:delta", 2760.0
);
jedis.zadd("leaderboard:global", scores);
// Retrieve top 3 players
Set<String> topPlayers = jedis.zrevrange("leaderboard:global", 0, 2);
topPlayers.forEach(System.out::println);
}
}
Spring Boot Integration
Spring Boot 2.0+ defaults to Lettuce — a Netty-based, thread-safe Redis client. Too opt-in explicitly:
<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>
Configure connection pooling in application.yml:
spring:
redis:
host: 192.168.100.110
port: 6379
password: 123456
timeout: 5000
lettuce:
pool:
max-active: 16
max-idle: 12
min-idle: 4
max-wait: 10000
To revert to Jedis (e.g., for legacy compatibility), exclude Lettuce and add Jedis explicitly:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.3</version>
</dependency>
Custom RedisTemplate Configuration
Define a type-safe RedisTemplate with JSON serialization:
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jsonSerializer.setObjectMapper(mapper);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
Utility Operations Wrapper
A simplified utility class abstracting common Redis operations:
@Component
public class CacheManager {
private final RedisTemplate<String, Object> redisTemplate;
public CacheManager(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public <T> T get(String key, Class<T> targetType) {
Object value = redisTemplate.opsForValue().get(key);
return value != null ? convert(value, targetType) : null;
}
public void set(String key, Object value, long ttlSeconds) {
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttlSeconds));
}
public void delete(String... keys) {
if (keys.length == 1) {
redisTemplate.delete(keys[0]);
} else {
redisTemplate.delete(Arrays.asList(keys));
}
}
public boolean exists(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
@SuppressWarnings("unchecked")
private <T> T convert(Object source, Class<T> targetType) {
if (targetType.isInstance(source)) return (T) source;
String json = new StringRedisSerializer().serialize((String) source);
try {
return new ObjectMapper().readValue(json, targetType);
} catch (Exception e) {
throw new RuntimeException("Deserialization failed for key: " + key, e);
}
}
}