Why Your Spring Redis Calls Hang: Uncovering the Hidden Connection‑Pool Pitfall

A week‑long API freeze in a sandbox environment was traced to a misconfigured Redis connection pool in a Spring Java application, where threads waited indefinitely for a Jedis resource, and the fix involved setting proper pool timeouts, using RedisCallback for scans, and correctly releasing connections.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Why Your Spring Redis Calls Hang: Uncovering the Hidden Connection‑Pool Pitfall

Hello, I'm Peng Lei.

In an internal sandbox environment the API became unresponsive for a week, with all requests hanging. Initial attempts to restart the application only provided temporary relief, prompting a deeper investigation.

Initial Investigation

Running top showed the machine was still alive. The next step was to examine JVM thread stacks with jstack. The most resource‑intensive threads were identified, but no obvious errors appeared in the logs or database.

JVM Stack Inspection

Using jstack 12798 | grep 12799 revealed several threads in a LOCK state without any business‑related code.

Redis Connection Issue

Further debugging showed that the threads were blocked while acquiring a Redis connection from the pool. The relevant code in JedisConnectionFactory is:

protected Jedis fetchJedisConnector() {
    try {
        if (usePool && pool != null) {
            return pool.getResource();
        }
        Jedis jedis = new Jedis(getShardInfo());
        jedis.connect();
        return jedis;
    } catch (Exception ex) {
        throw new RedisConnectionFailureException("Cannot get Jedis connection", ex);
    }
}

When pool.getResource() is called, the thread enters a waiting state:

public T getResource() {
    try {
        return internalPool.borrowObject();
    } catch (Exception e) {
        throw new JedisConnectionException("Could not get a resource from the pool", e);
    }
}

The pool implementation contains logic that, if borrowMaxWaitMillis < 0, will loop indefinitely, causing the observed hang.

Thread‑Level Diagnosis with Arthas

Installing Arthas and running thread revealed many http-nio threads stuck in WAITING state, confirming that all incoming API requests were blocked on the Redis pool.

Further analysis with thread -b (blocked‑by) showed no direct blocker, indicating the issue stemmed from the pool configuration itself.

Fixing the Pool Configuration

Adding a proper maxWaitMillis value to the JedisPoolConfig (e.g., 2000 ms) prevented the infinite wait:

JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxWaitMillis(2000);
jedisConnectionFactory.setPoolConfig(config);
jedisConnectionFactory.afterPropertiesSet();

After redeploying, the API resumed normal operation, but occasional 500 errors persisted.

Root Cause of the 500 Errors

The stack trace showed RedisConnectionFailureException: Cannot get Jedis connection originating from Spring’s StringRedisTemplate usage:

Cursor c = stringRedisTemplate.getConnectionFactory().getConnection().scan(options);
while (c.hasNext()) { ... }

Calling getConnection() directly rents a Redis connection without returning it, leaving the pool exhausted.

Recommended Approach

Instead of accessing the raw connection, use RedisCallback to execute commands and let Spring manage the connection lifecycle:

stringRedisTemplate.execute(new RedisCallback<Cursor>() {
    @Override
    public Cursor doInRedis(RedisConnection connection) throws DataAccessException {
        return connection.scan(options);
    }
});

When a raw connection is required, release it explicitly:

RedisConnectionUtils.releaseConnection(conn, factory);

Avoid using the KEYS command in production and configure the Redis pool with sensible limits to prevent silent deadlocks.

Conclusion

The issue was caused by an improperly configured Redis connection pool and the misuse of StringRedisTemplate.getConnectionFactory().getConnection(). Proper pool timeout settings and using Spring‑managed callbacks resolve the hanging API problem.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingJavaThread DumpredisspringConnection Pool
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.