springboot-how to solve JedisShardInfo or JedisPoolConfig not found exception with spring boot application ?
1. Problem
When we run a springboot application that access a redis server, sometimes, we would get exception or error as follows:
/Users/bswen/private/bw/bswen-github/bswen-springboot23/app10/src/main/java/com/bswen/app10/config/RedisConfiguration.java:32: Error: can not find JedisPoolConfig
return new JedisConnectionFactory(config);
^
class redis.clients.jedis.JedisPoolConfig not found
Or this error:
/Users/bswen/private/bw/bswen-github/bswen-springboot23/app10/src/main/java/com/bswen/app10/config/RedisConfiguration.java:54: Error: can not find JedisShardInfo
return new JedisConnectionFactory(config);
^
class redis.clients.jedis.JedisShardInfo not found
Or this error:
java.lang.ClassNotFoundException: redis.clients.jedis.util.Pool
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_121]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_121]
The application is correctly configured, I promise!!!
2. Environment
- Springboot 2.x
3. Code
3.1 The project dependencies
We use maven as the dependency management tool, this is the pom.xml:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<start-class>Application</start-class>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<artifactId>spring-boot-multiredis2</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
You can see that we depend on these:
- spring boot 2.4.2
- spring data redis
3.2 The redis configuration class
We define a class for redis configuration as follows:
public class RedisCommonProperty {
private String host;
private int port;
private int database;
//getters and setters ignored
}
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class Redis1Property extends RedisCommonProperty {
}
The above two classes are used to load property files into java. We still need to tell spring boot how to build redis connection and redisTemplate:
@Configuration
public class Redis1Configuration {
@Autowired
private Redis1Property redis1Property;
@Primary
@Bean(name = "redis1ConnectionFactory")
public RedisConnectionFactory redis1ConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(
redis1Property.getHost(), redis1Property.getPort());
return new JedisConnectionFactory(config);
}
@Bean(name = "redis1StringRedisTemplate")
public StringRedisTemplate userStringRedisTemplate(@Qualifier("redis1ConnectionFactory") RedisConnectionFactory cf) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(cf);
return stringRedisTemplate;
}
@Bean(name = "redis1RedisTemplate")
public RedisTemplate userRedisTemplate(@Qualifier("redis1ConnectionFactory") RedisConnectionFactory cf) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(cf);
//setSerializer(stringRedisTemplate);
return stringRedisTemplate;
}
}
The above code’s key points are as follows:
- The redis1ConnectionFactory method returns a RedisConnectionFactory, which is required by the redisTemplate, it’s the connection factory for redis connections.
- The userStringRedisTemplate and userRedisTemplate are both RedisTemplate class to be used by applications.
3.3 Test the redis connection
Now we can test the connection, the code is as follows:
@Component
public class MultiRedisTestRunner implements CommandLineRunner {
private final static Logger logger = LoggerFactory.getLogger(MultiRedisTestRunner.class);
@Autowired
@Qualifier("redis1StringRedisTemplate")
private StringRedisTemplate stringRedisTemplate;
//@Override
public void run(String... strings) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
try {
for (int i = 0; i < 1; i++) {
logger.info("=====================================================================");
logger.info("start loop " + i);
String key = "key" + i;
stringRedisTemplate.opsForValue().set(key, "value" + i);
String primaryKeyValue = stringRedisTemplate.opsForValue().get(key);
logger.info("=====================================================================");
logger.info(String.format("read from the redis1, key %s value is %s", key, primaryKeyValue));
}
}finally {
latch.await();
}
}
}
When we run the code, the exception occurred:
class redis.clients.jedis.JedisPoolConfig not found
class redis.clients.jedis.JedisShardInfo not found
How to solve this problem?
4. Reason
JedisPoolConfig is needed when we use Jedis Connector to connect from springboot to redis server. In Spring Boot 2.0, spring-boot-starter-data-redis uses Lettuce connector by default instead of Jedis connector. To use Jedis connector, we need to exclude Lettuce from spring-boot-starter-data-redis.
5. Solution
We should change our pom.xml as follows:
<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>2.9.0</version>
</dependency>
Rerun our tests, We got this exception:
rg.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'multiRedisTestRunner': Unsatisfied dependency expressed through field 'stringRedisTemplate'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'redis1StringRedisTemplate' defined in class path resource [com/bswen/sbr2/config/Redis1Configuration.class]: Unsatisfied dependency expressed through method 'userStringRedisTemplate' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redis1ConnectionFactory' defined in class path resource [com/bswen/sbr2/config/Redis1Configuration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.RedisConnectionFactory]: Factory method 'userRedisConnectionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: redis/clients/jedis/util/Pool
...
Caused by: java.lang.ClassNotFoundException: redis.clients.jedis.util.Pool
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_121]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_121]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) ~[na:1.8.0_121]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_121]
... 58 common frames omitted
After some googling, I found that we should upgrade our jedis dependency to match the spring boot version:
<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>3.3.0</version>
</dependency>
Rerun our test, we got this:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.2)
2021-03-06 21:40:13.024 INFO 22247 --- [ main] com.bswen.sbr2.MultiRedisApplication2 : Starting MultiRedisApplication2 using Java 1.8.0_121 on MBP-bswen with PID 22247 (/Users/bswen/private/bw/bswen-github/bswen-project/spring-boot-multiredis2/target/classes started by bswen in /Users/bswen/private/bw/bswen-github/bswen-project)
2021-03-06 21:40:13.029 INFO 22247 --- [ main] com.bswen.sbr2.MultiRedisApplication2 : No active profile set, falling back to default profiles: default
2021-03-06 21:40:13.714 INFO 22247 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-03-06 21:40:13.717 INFO 22247 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2021-03-06 21:40:13.746 INFO 22247 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 9 ms. Found 0 Redis repository interfaces.
2021-03-06 21:40:14.310 INFO 22247 --- [ main] com.bswen.sbr2.MultiRedisApplication2 : Started MultiRedisApplication2 in 1.719 seconds (JVM running for 2.252)
2021-03-06 21:40:14.312 INFO 22247 --- [ main] c.b.sbr2.service.MultiRedisTestRunner : =====================================================================
2021-03-06 21:40:14.312 INFO 22247 --- [ main] c.b.sbr2.service.MultiRedisTestRunner : start loop 0
2021-03-06 21:40:14.381 INFO 22247 --- [ main] c.b.sbr2.service.MultiRedisTestRunner : =====================================================================
2021-03-06 21:40:14.381 INFO 22247 --- [ main] c.b.sbr2.service.MultiRedisTestRunner : read from the redis1, key key0 value is value0
It works!
6. Summary
Now, we know that the ‘JedisPoolConfig not found’ or ‘JedisShardInfo not found’ exceptions are caused by the conflict of the default lettuce and jedis connector of spring boot applications. If we insist on Jedis, we should exclude Lettuce. And for compatible reason, we should upgrade to jedis client library to 3.30+ to match spring boot version. Thanks for your reading.
All the code are uploaded to github.com, please check this link.