Skip to main content

Spring Data Redis

Introduction

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store that can be used as a database, cache, message broker, and more. Spring Data Redis is part of the larger Spring Data family and provides easy configuration and access to Redis from Spring applications.

In this guide, you'll learn:

  • What Redis is and why it's useful in Spring applications
  • How to configure Spring Data Redis
  • Basic Redis operations
  • Using Redis for caching
  • Redis data structures and how to work with them
  • Real-world use cases for Redis in Spring applications

What is Redis?

Redis is an in-memory key-value store known for its flexibility, performance, and wide range of data structure support. Unlike traditional databases, Redis keeps its entire dataset in memory, making it extremely fast for read and write operations.

Key features of Redis include:

  • Lightning-fast performance (typically <1ms operations)
  • Support for various data structures (strings, lists, sets, hashes, etc.)
  • Built-in replication and high availability
  • Persistence options to save data to disk
  • Atomic operations on complex data types
  • Pub/Sub messaging capabilities
  • Lua scripting support

Spring Data Redis provides a convenient way to interact with Redis from your Spring applications, abstracting away the low-level details.

Getting Started with Spring Data Redis

Adding Dependencies

To use Spring Data Redis in your Spring Boot project, add the following dependency to your Maven pom.xml:

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

Or in Gradle build.gradle:

groovy
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

Basic Configuration

Spring Boot provides auto-configuration for Redis, but you'll need to specify connection details in your application.properties or application.yml file:

properties
# Redis server address
spring.redis.host=localhost
spring.redis.port=6379

# Optional authentication
spring.redis.password=your-password

# Connection pool settings
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

For more complex setups, you can define a custom Redis configuration:

java
@Configuration
public class RedisConfig {

@Bean
public RedisConnectionFactory redisConnectionFactory() {
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory();
connectionFactory.setHostName("localhost");
connectionFactory.setPort(6379);
return connectionFactory;
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}
}

Basic Redis Operations

Spring Data Redis provides RedisTemplate and StringRedisTemplate classes for performing operations on Redis.

String Operations

java
@Service
public class ProductService {

private final StringRedisTemplate redisTemplate;

public ProductService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void setProductName(String productId, String name) {
redisTemplate.opsForValue().set("product:" + productId + ":name", name);
}

public String getProductName(String productId) {
return redisTemplate.opsForValue().get("product:" + productId + ":name");
}

public void setProductWithExpiry(String productId, String name) {
// Set value with 1 hour expiration
redisTemplate.opsForValue().set(
"product:" + productId + ":name",
name,
1,
TimeUnit.HOURS
);
}
}

Example usage:

java
productService.setProductName("12345", "Smartphone");
String name = productService.getProductName("12345");
// Output: Smartphone

Working with Objects

To store complex objects in Redis, you'll need to configure Redis to serialize/deserialize your objects:

java
@RedisHash("Product")
public class Product implements Serializable {

@Id
private String id;
private String name;
private double price;
private String category;

// Constructors, getters, setters, etc.
}

Now you can use Spring Data Redis repositories:

java
public interface ProductRepository extends CrudRepository<Product, String> {
List<Product> findByCategory(String category);
}

Example usage:

java
@Service
public class CatalogService {

private final ProductRepository productRepository;

public CatalogService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

public void addProduct(Product product) {
productRepository.save(product);
}

public Product getProduct(String id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found"));
}

public List<Product> getProductsByCategory(String category) {
return productRepository.findByCategory(category);
}
}

Working with Redis Data Structures

Redis supports various data structures, and Spring Data Redis provides operations for all of them.

Lists

java
@Service
public class NotificationService {

private final StringRedisTemplate redisTemplate;

public NotificationService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void addNotification(String userId, String message) {
redisTemplate.opsForList().leftPush("user:" + userId + ":notifications", message);
}

public List<String> getLatestNotifications(String userId, int count) {
return redisTemplate.opsForList().range("user:" + userId + ":notifications", 0, count - 1);
}
}

Sets

java
@Service
public class TagService {

private final StringRedisTemplate redisTemplate;

public TagService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void addProductTag(String productId, String tag) {
redisTemplate.opsForSet().add("product:" + productId + ":tags", tag);
}

public Set<String> getProductTags(String productId) {
return redisTemplate.opsForSet().members("product:" + productId + ":tags");
}

public Set<String> findProductsWithAllTags(String... tags) {
String[] keys = Arrays.stream(tags)
.map(tag -> "tag:" + tag + ":products")
.toArray(String[]::new);

return redisTemplate.opsForSet().intersect(Arrays.asList(keys));
}
}

Hashes

Hashes are perfect for storing objects with multiple fields:

java
@Service
public class UserProfileService {

private final StringRedisTemplate redisTemplate;

public UserProfileService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void updateUserProfile(String userId, Map<String, String> profileData) {
redisTemplate.opsForHash().putAll("user:" + userId + ":profile", profileData);
}

public void updateUserAttribute(String userId, String attribute, String value) {
redisTemplate.opsForHash().put("user:" + userId + ":profile", attribute, value);
}

public Map<Object, Object> getUserProfile(String userId) {
return redisTemplate.opsForHash().entries("user:" + userId + ":profile");
}

public String getUserAttribute(String userId, String attribute) {
return (String) redisTemplate.opsForHash().get("user:" + userId + ":profile", attribute);
}
}

Using Redis for Caching

One of the most common use cases for Redis in Spring applications is caching. Spring Data Redis integrates seamlessly with Spring's caching abstraction.

Enabling Redis Caching

First, enable caching in your application:

java
@SpringBootApplication
@EnableCaching
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

Configure Redis as the cache manager:

java
@Configuration
public class CacheConfig {

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // Set default expiration
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfig)
.build();
}
}

Using the Cache

With Redis configured as the cache manager, you can use Spring's caching annotations:

java
@Service
public class ProductCatalogService {

private final ProductRepository productRepository;

public ProductCatalogService(ProductRepository productRepository) {
this.productRepository = productRepository;
}

@Cacheable(value = "products", key = "#id")
public Product getProductById(String id) {
// This will be executed only if the result isn't in cache
System.out.println("Fetching product from database for id: " + id);
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found"));
}

@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
System.out.println("Updating product: " + product.getId());
return productRepository.save(product);
}

@CacheEvict(value = "products", key = "#id")
public void deleteProduct(String id) {
System.out.println("Deleting product: " + id);
productRepository.deleteById(id);
}

@CacheEvict(value = "products", allEntries = true)
public void clearProductCache() {
System.out.println("Clearing all product cache entries");
}
}

Calls to getProductById with the same ID will return the cached result without executing the method body after the first call. This can significantly improve performance for frequently accessed data.

Real-World Use Cases

Session Storage

Redis is often used to store user sessions in distributed applications:

java
@Configuration
public class SessionConfig {

@Bean
public RedisIndexedSessionRepository sessionRepository(RedisConnectionFactory connectionFactory) {
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(connectionFactory);
sessionRepository.setDefaultMaxInactiveInterval(1800); // 30 minutes
return sessionRepository;
}
}

In application.properties:

properties
spring.session.store-type=redis
server.servlet.session.timeout=30m

Rate Limiting

Redis can be used to implement rate limiting for APIs:

java
@Service
public class RateLimiterService {

private final StringRedisTemplate redisTemplate;

public RateLimiterService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

/**
* Check if the request is allowed based on rate limiting
* @param key identifier (e.g., user ID, IP address)
* @param maxRequests maximum allowed requests in the window
* @param timeWindowSeconds time window in seconds
* @return true if request is allowed, false otherwise
*/
public boolean isAllowed(String key, int maxRequests, int timeWindowSeconds) {
String redisKey = "ratelimit:" + key;

Long currentCount = redisTemplate.opsForValue().increment(redisKey);

if (currentCount == 1) {
// First request in window, set expiration
redisTemplate.expire(redisKey, timeWindowSeconds, TimeUnit.SECONDS);
}

return currentCount <= maxRequests;
}
}

Usage in a controller:

java
@RestController
@RequestMapping("/api")
public class ApiController {

private final RateLimiterService rateLimiterService;
private final ProductService productService;

public ApiController(RateLimiterService rateLimiterService, ProductService productService) {
this.rateLimiterService = rateLimiterService;
this.productService = productService;
}

@GetMapping("/products/{id}")
public ResponseEntity<?> getProduct(@PathVariable String id, HttpServletRequest request) {
String ipAddress = request.getRemoteAddr();

if (!rateLimiterService.isAllowed(ipAddress, 10, 60)) {
return ResponseEntity.status(429).body("Too many requests, please try again later.");
}

return ResponseEntity.ok(productService.getProduct(id));
}
}

Leaderboards

Redis sorted sets are perfect for implementing leaderboards:

java
@Service
public class LeaderboardService {

private final StringRedisTemplate redisTemplate;

public LeaderboardService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void updateScore(String leaderboardId, String userId, double score) {
redisTemplate.opsForZSet().add(leaderboardId, userId, score);
}

public double getScore(String leaderboardId, String userId) {
Double score = redisTemplate.opsForZSet().score(leaderboardId, userId);
return score == null ? 0.0 : score;
}

public List<String> getTopUsers(String leaderboardId, int count) {
// Get users with highest scores
return redisTemplate.opsForZSet()
.reverseRange(leaderboardId, 0, count - 1)
.stream()
.collect(Collectors.toList());
}

public long getUserRank(String leaderboardId, String userId) {
Long rank = redisTemplate.opsForZSet().reverseRank(leaderboardId, userId);
return rank == null ? -1 : rank + 1;
}
}

Summary

Spring Data Redis provides a powerful way to integrate Redis into your Spring applications. In this guide, we've covered:

  • Setting up Spring Data Redis in a Spring Boot application
  • Working with Redis data structures: strings, lists, sets, and hashes
  • Using Redis for caching in Spring
  • Real-world applications of Redis including session storage, rate limiting, and leaderboards

Redis excels at scenarios requiring high-speed data access, caching, real-time analytics, and managing distributed state. By leveraging Spring Data Redis, you can easily incorporate these capabilities into your Spring applications with minimal boilerplate code.

Additional Resources

Exercises

  1. Set up a local Redis instance and configure it with Spring Data Redis.
  2. Build a simple caching layer for a database-intensive operation.
  3. Implement a view counter for blog posts using Redis.
  4. Create a user session management system using Redis.
  5. Build a rate limiter to protect your APIs from abuse.
  6. Design and implement a real-time leaderboard for a game or competition.

Happy coding with Spring Data Redis!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)