Listening to Events From Redis in Your Spring Boot Application

Recently, while developing a new feature that my team needed to implement, we came across the following problem: After our data expired and was subsequently deleted from the cache (Redis), a business flow needed to be triggered. During the initial tech…


This content originally appeared on Level Up Coding - Medium and was authored by Lucas Fernandes

Recently, while developing a new feature that my team needed to implement, we came across the following problem: After our data expired and was subsequently deleted from the cache (Redis), a business flow needed to be triggered. During the initial technical refinement, the team discussed ways to implement this rule, and some ideas emerged, such as a simple CronJob or even Quartz.

However, after conducting some research, we discovered a little-known but extremely useful feature which is KeySpaceNotifications. This feature allows you to listen to key-related events such as when keys are set, deleted, or expired. These notifications enable applications to trigger real-time business logic based on Redis events. The knowledge gained from this feature motivated me to write this article for my dear Medium readers.

In this solution, we will use the following technologies: Java, Spring Boot (3.x), Redis, Docker and Gradle

Introduction

In the world of modern software development, performance and scalability are critical. Applications need to handle millions of user requests per second, deliver responses in milliseconds, and ensure minimal latency. This is where Redis, an open-source, in-memory data store, plays a pivotal role.

Redis (Remote Dictionary Server) is designed for high-performance operations, offering sub-millisecond response times. It is widely adopted across industries for use cases like caching, real-time analytics, session management, and pub/sub messaging.

Why Redis?

Redis excels in scenarios where speed is a priority, thanks to its in-memory architecture. Unlike traditional databases that rely on disk I/O for operations, Redis keeps data in RAM, significantly reducing access times. This makes it an ideal choice for caching frequently accessed data, reducing the load on primary databases, and improving overall application performance.

Redis as a Cache

Caching is a strategy used to store frequently accessed data closer to the application to reduce latency and computational overhead. Redis is particularly well-suited for caching because:

  • Speed: Redis performs millions of operations per second with minimal latency
  • Data Structures: It supports advanced data types like hashes, lists, sets, sorted sets, and more, making it versatile for complex caching scenarios
  • TTL (Time-to-Live): Redis allows setting expiration times on keys, ensuring that cached data is automatically removed when it becomes stale

Large-Scale Adoption of Redis

Redis has become a cornerstone technology for global organizations. Companies like Twitter, Uber, Netflix, and Pinterest rely on Redis to power mission-critical systems. Its adoption is driven by:

  • Scalability: Redis can handle millions of concurrent connections, making it ideal for distributed systems
  • Flexibility: With features like keyspace notifications, pub/sub messaging, and Lua scripting, Redis is more than just a cache
  • Open-Source and Cloud-Native: Redis is open-source and has robust managed services like AWS ElastiCache, Azure Cache for Redis, and Google Cloud Memorystore

What Are Redis Keyspace Notifications?

Keyspace Notifications in Redis allow clients to subscribe to Pub/Sub channels that broadcast events related to keys. These events can be triggered for operations such as setting a key, expiring a key, or deleting a key.

Keyspace Notifications are disabled by default and can be enabled by configuring the notify-keyspace-events parameter in Redis. Notifications are categorized into keyspace events (specific to keys) and keyevent notifications (specific to actions).

Which Events Can We To Listen

Here are some common patterns you can use to listen to Redis events:

The @* part refers to the database index, enabling flexibility across multiple databases.

Configuring Redis

Photo byThisisEngineering in Unsplash

Enabling Keyspace Notifications in Redis

To enable Keyspace Notifications, update the notify-keyspace-events parameter in your Redis configuration file (redis.conf) or use the CONFIG SET command. You can customize the notifications using the notify-keyspace-events configuration string. The most common options include:

  • K: Keyspace events.
  • E: Keyevent notifications.
  • A: Alias for all events.
  • $: String commands (set, append, etc.).
  • g: Generic commands (del, expire, etc.).
  • x: Expiry-related events.
  • l: List operations.
  • h: Hash operations.
  • z: Sorted set operations.

Example 1: This configuration enables notifications for expired (E) and evicted (x) keys.

CONFIG SET notify-keyspace-events Ex

Example 2: To enable all notifications, use:

CONFIG SET notify-keyspace-events KEA

Deploying Redis Locally

You can use the following docker-compose file to running locally:

version: '3.9'
services:
redis:
image: redis/redis-stack:7.2.0-v4
container_name: redis
environment:
- REDIS_ARGS=--notify-keyspace-events KEA # Enable Redis Keyspace for All Notifications
ports:
- 6379:6379
- 8001:8001

Accessing via Browse

Screenshot from Author. http://localhost:8001

Hands-On with Java and Spring Boot

Let’s implement a Spring Boot application to listen to Redis key events and apply business logic.

Prerequisites:

  • Redis installed and running
  • Java 21+
  • Spring Boot (Spring Data Redis)

Gradle Example:

plugins {
id 'java'
id 'org.springframework.boot' version '3.4.1'
id 'io.spring.dependency-management' version '1.1.7'
}

group = 'br.com.ldf.medium'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.flywaydb:flyway-core'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
useJUnitPlatform()
}

Spring Configuration for Redis

Configure your application.yml

Application.yml:

spring:
application:
name: events-from-redis
threads:
virtual:
enabled: true
datasource:
url: jdbc:h2:mem:testdb;MODE=PostgreSQL
driverClassName: org.h2.Driver
username: sa
password:
jpa:
show-sql: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
cache:
type: redis
redis:
time-to-live: 60000 # 1 minute
data:
redis:
host: localhost
port: 6379

Create a configuration class to set up Redis and enable Pub/Sub.

RedisConfig

@EnableCaching
@Configuration
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
public class RedisConfig {

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}

@Bean
public MessageListenerAdapter listenerAdapter(@Lazy MessageListener listener) {
return new MessageListenerAdapter(listener);
}

@Bean
public RedisMessageListenerContainer container(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter
) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// Add listeners for all topics
Arrays.stream(RedisPatternTopic.values())
.forEach(
topic -> container.addMessageListener(listenerAdapter, new PatternTopic(topic.getTopic()))
);
return container;
}

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

RedisSerializer<String> stringSerializer = new StringRedisSerializer();
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();

template.setKeySerializer(stringSerializer);
template.setValueSerializer(jsonSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(jsonSerializer);

template.afterPropertiesSet();
return template;
}

}

Explanation:

The RedisConfig class is a Spring configuration class that sets up Redis-related beans and configurations for your Spring Boot application. Here’s a breakdown of its components:

Annotations:

  • @EnableCaching: Enables Spring’s annotation-driven cache management capability
  • @Configuration: Indicates that the class can be used by the Spring IoC container as a source of bean definitions
  • @EnableRedisRepositories: Enables Redis repositories and keyspace events

Beans

  • redisConnectionFactory(): Creates a LettuceConnectionFactory bean, which provides a connection to the Redis server
  • listenerAdapter(MessageListener listener): Creates a MessageListenerAdapter bean, which adapts a MessageListener to the Redis message listener infrastructure
  • container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter): Creates a RedisMessageListenerContainer bean, which manages Redis message listeners and their subscriptions to topics
  • redisTemplate(RedisConnectionFactory connectionFactory): Creates a RedisTemplate bean, which provides high-level abstractions for Redis interactions, including serialization configurations for keys and values.

RedisPatternTopic

/**
* Enum to represent the Redis pattern topics.
*/
@Getter
public enum RedisPatternTopic {
EXPIRED_EVENT("__keyevent@*__:expired", "expired"),
SET_EVENT("__keyevent@*__:set", "set"),
DELETE_EVENT("__keyevent@*__:del", "del"),
EVICT_EVENT("__keyevent@*__:evict", "evict");

private final String topic;
private final String operation;

RedisPatternTopic(String topic, String operation) {
this.topic = topic;
this.operation = operation;
}

public static RedisPatternTopic from(String topic) {
for (RedisPatternTopic redisPatternTopic : values()) {
if (redisPatternTopic.operation.equals(topic.split(":")[1])) {
return redisPatternTopic;
}
}
throw new IllegalArgumentException("Invalid topic: " + topic);
}
}

RedisEventListener

/**
* Listener to handle expired events from Redis.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisEventListener implements MessageListener {

private final Set<HandlerRedisEventStrategy> handlers;

@Override
public void onMessage(Message message, byte[] pattern) {
var eventTopic = RedisPatternTopic.from(new String(message.getChannel()));
log.info("message:{}, event-topic={}", message, eventTopic);

handlers.stream()
.filter(handler -> handler.accept(eventTopic))
.findFirst()
.ifPresent(handler -> handler.handle(new String(message.getBody())));
}
}

Explanation:

The RedisEventListener class is a component that listens for Redis keyspace events and handles them using a set of strategies. Here’s a breakdown of its components:

  • handlers: A set of HandlerRedisEventStrategy instances that define how to handle different Redis events
  • onMessage(Message message, byte[] pattern): This method is called when a message is received from Redis. Converts the message channel to a RedisPatternTopic and delivery to respective handler

HandlerRedisEventStrategy:

public interface HandlerRedisEventStrategy {

boolean accept(RedisPatternTopic redisPatternTopic);

void handle(String key);
}

HandlerRedisEventStrategySet:

@Slf4j
@Component
public class HandlerRedisEventStrategySet implements HandlerRedisEventStrategy {

@Override
public boolean accept(RedisPatternTopic redisPatternTopic) {
return RedisPatternTopic.SET_EVENT.equals(redisPatternTopic);
}

@Override
public void handle(String key) {
log.info("Handling set key: {}", key);
}
}

HandlerRedisEventStrategyDel:

@Slf4j
@Component
public class HandlerRedisEventStrategyDel implements HandlerRedisEventStrategy {

@Override
public boolean accept(RedisPatternTopic redisPatternTopic) {
return RedisPatternTopic.DELETE_EVENT.equals(redisPatternTopic);
}

@Override
public void handle(String key) {
log.info("Handling expired key: {}", key);
}
}

HandlerRedisEventStrategyExpired:

@Slf4j
@Component
public class HandlerRedisEventStrategyExpired implements HandlerRedisEventStrategy {

@Override
public boolean accept(RedisPatternTopic redisPatternTopic) {
return RedisPatternTopic.EXPIRED_EVENT.equals(redisPatternTopic);
}

@Override
public void handle(String key) {
log.info("Handling expired key: {}", key);
}
}

Testing

To test the Redis pub/sub integration, we can create a simple EmployeeController for managing CRUD operations. This controller allows us to perform basic operations while demonstrating how to publish events to Redis channels.

EmployeeController:

@RestController
@RequestMapping(EmployeeController.EMPLOYEES_API_PATH)
@RequiredArgsConstructor
public class EmployeeController {

public static final String EMPLOYEES_API_PATH = "/api/employees";

private final EmployeeChangeUseCase employeeChangeUseCase;
private final EmployeeSearchUseCase employeeSearchUseCase;
private final EmployeeApplicationMapper mapper;

@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Employee> getById(@PathVariable Long id) {
return ResponseEntity.ok(employeeSearchUseCase.getById(id));
}

@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> create(@RequestBody @Validated EmployeeRequest request) {
var employee = employeeChangeUseCase.create(mapper.mapToModel(request));
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(employee.getId())
.toUri();
return ResponseEntity.created(location).build();
}

@PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> update(@PathVariable Long id, @RequestBody @Validated EmployeeRequest request) {
employeeChangeUseCase.update(id, mapper.mapToModel(request));
return ResponseEntity.noContent().build();
}

@DeleteMapping(value = "/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
employeeChangeUseCase.delete(id);
return ResponseEntity.noContent().build();
}
}

Inside of ours useCases, we have the cache operations:

EmployeeSearchUseCaseImpl:

@Service
@RequiredArgsConstructor
@FieldDefaults(level = lombok.AccessLevel.PRIVATE, makeFinal = true)
public class EmployeeSearchUseCaseImpl implements EmployeeSearchUseCase {

EmployeeProvider employeeProvider;

@Cacheable(cacheNames = "employeeById", key = "#id")
public Employee getById(Long id) {
return employeeProvider.getById(id);
}

}

EmployeeChangeUseCaseImpl:

@Service
@RequiredArgsConstructor
@FieldDefaults(level = lombok.AccessLevel.PRIVATE, makeFinal = true)
public class EmployeeChangeUseCaseImpl implements EmployeeChangeUseCase {

EmployeeProvider employeeProvider;

public Employee create(Employee employee) {
return employeeProvider.save(employee);
}

@CachePut(cacheNames = "employeeById", key = "#id")
public Employee update(Long id, Employee employee) {
return employeeProvider.update(id, employee);
}

@CacheEvict(cacheNames = "employeeById", key = "#id")
public void delete(Long id) {
employeeProvider.delete(id);
}
}

Running

Lets simulate the following operations:

  1. Creating new employee
  2. Searching by Id
  3. Deleting by Id
  4. Creating new employee
  5. Searching by Id
  6. Redis Expired Event deleting key in cache
Creating new Employee
Searching by Id
Application Console
Deleting Employee
Application Console
Creating new Employee
Searching by Id
Application Console
project reference: https://github.com/Medium-Artigos/events-from-redis

Conclusion

Redis is much more than a simple key-value store; its rich feature set enables developers to build highly responsive, scalable, and efficient applications. Among these features, Keyspace Notifications stand out as a powerful mechanism for real-time event-driven architectures. By leveraging these notifications, applications can react to changes in Redis keys — such as expirations, deletions, and updates — and trigger custom business logic instantly.

In this article, we explored:

  • The fundamentals of Redis Keyspace Notifications and their practical use cases.
  • How to configure Redis to enable these notifications.
  • A hands-on example of integrating Redis Keyspace Notifications into a Spring Boot application.

Keyspace Notifications are particularly useful in scenarios like cache invalidation, event-driven workflows, and monitoring system changes. However, it’s essential to design your application carefully to avoid unnecessary load on your Redis server, especially when working with large-scale data or high-frequency events.

Feel free to leave comments or suggestions, and share with other developers who might benefit from this solution!

Follow me for more exciting content

➡️ Medium

➡️ LinkedIn

➡️ Substack

☕Buy me a Coffee ☕


Listening to Events From Redis in Your Spring Boot Application was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Lucas Fernandes


Print Share Comment Cite Upload Translate Updates
APA

Lucas Fernandes | Sciencx (2025-01-17T19:02:12+00:00) Listening to Events From Redis in Your Spring Boot Application. Retrieved from https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/

MLA
" » Listening to Events From Redis in Your Spring Boot Application." Lucas Fernandes | Sciencx - Friday January 17, 2025, https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/
HARVARD
Lucas Fernandes | Sciencx Friday January 17, 2025 » Listening to Events From Redis in Your Spring Boot Application., viewed ,<https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/>
VANCOUVER
Lucas Fernandes | Sciencx - » Listening to Events From Redis in Your Spring Boot Application. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/
CHICAGO
" » Listening to Events From Redis in Your Spring Boot Application." Lucas Fernandes | Sciencx - Accessed . https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/
IEEE
" » Listening to Events From Redis in Your Spring Boot Application." Lucas Fernandes | Sciencx [Online]. Available: https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/. [Accessed: ]
rf:citation
» Listening to Events From Redis in Your Spring Boot Application | Lucas Fernandes | Sciencx | https://www.scien.cx/2025/01/17/listening-to-events-from-redis-in-your-spring-boot-application/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.