spring-bootredisspring-data-redisjedis

Migrating spring-data-redis from 2.6.2 to 3.1. Unable to add/connect Message Listener


During migration Spring Boot from version 2.6.2 to 3.1, I was switching also spring-data-redis from 2.6.2 to 3.1 and have encountered problem that Message Listener bean can not be started anymore. Exception itself does not describe too much as it only says that:

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'jedisMessageListenerContainer'","logger_name":"org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"}

Where jedisMessageListenerContainer looks as following:

    @Bean
    RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                                RedisKeyExpirationListener redisListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        container.addMessageListener(redisListener, new PatternTopic(
                "__keyevent@0__:expired"));
        container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
        return container;
    }

RedisKeyExpirationListener is a class that implements MessageListener interface from the spring-data-redis

After some debugging I have find out only that RedisMessageListenerContainer is catching TimeoutException and then throwing

IllegalStateException("Subscription registration timeout exceeded", e);

Which is later being caught by Bean Processor and parsed into Failed to start bean exception message.

I have tried to increase the MaxSubscriptionRegistrationWaitingTime for the container (up to 60 seconds) but it just resulted in longer waiting time for same exception.


I am using the Message Listener together with Jedis (version 4.4.2 but also checked with 4.3.2 which should be compatible) as the connection factory and spring-boot-starter-data-redis with version 3.1.0. Here is implementation of the connection factory:

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        var jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMinIdle(0);
        jedisPoolConfig.setMaxIdle(8);
        return jedisPoolConfig;
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {

        var jedisClientConfiguration = JedisClientConfiguration.builder()
                .usePooling()
                .poolConfig(jedisPoolConfig)
                .and()
                .readTimeout(Duration.ofMillis(1000L))
                .useSsl()
                .build();

As additional test I have just removed the addMessageListener invocation on the Redis container and application was able to work with the Redis itself. The problem only starts to appear when I add the Message Listener to the RedisMessageListenerContainer.

Also when bringing back spring to version 2.6.2 together with spring-data-redis Message Listener works and I can see that expired events are being handled.

Can someone help why this happening and why I cannot initialize the beans once want to add this listener?

I tried to:

  1. Update the Jedis, Spring Boot, spring-data-redis and spring-boot-starter-data-redis dependencies to the newest as also to one that are compatible with Spring Boot version 3
  2. Increase the connection pool and timeouts.
  3. Remove the listener to check if application starts to work (It worked without listener)
  4. Change the implementation according to the spring-data-redis documentation to the following one:
    @Bean
    RedisKeyExpirationListener redisListener() {
        return new RedisKeyExpirationListener();
    }

    @Bean
    MessageListenerAdapter redisListenerAdapter(RedisKeyExpirationListener redisListener) {
        return new MessageListenerAdapter(redisListener, "handleMessage");
    }

    @Bean
    RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                                MessageListenerAdapter redisListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        container.addMessageListener(redisListenerAdapter, new PatternTopic(
                "__keyevent@0__:expired"));
        container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
        return container;
    }

where now RedisKeyExpirationListener does implement own interface:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Message message, byte[] pattern);
}

instead implementing MessageListener interface from the spring-data-redis


Solution

  • If you are expierencing Failed to start bean exception messageduring local testing of Redis functionalities, try to comment .useSSl() in jedisConnectionFactory class.