Jedis is a blazingly small and sane Redis java client.
This document covers the comprehensive exception hierarchy in Jedis for robust error handling, including connection exceptions, cluster-specific exceptions, authentication failures, and best practices for error recovery.
Root exception class for all Jedis-specific exceptions.
public class JedisException extends RuntimeException {
/**
* Creates exception with message
* @param message Error message
*/
public JedisException(String message);
/**
* Creates exception with message and cause
* @param message Error message
* @param cause Underlying cause
*/
public JedisException(String message, Throwable cause);
/**
* Creates exception with cause
* @param cause Underlying cause
*/
public JedisException(Throwable cause);
/**
* Gets error message
* @return Exception message
*/
@Override
public String getMessage();
/**
* Gets underlying cause
* @return Exception cause
*/
@Override
public Throwable getCause();
}Exception for Redis server errors and data-related issues.
public class JedisDataException extends JedisException {
/**
* Creates data exception with message
* @param message Error message from Redis server
*/
public JedisDataException(String message);
/**
* Creates data exception with message and cause
* @param message Error message
* @param cause Underlying cause
*/
public JedisDataException(String message, Throwable cause);
}Exception for connection-related failures.
public class JedisConnectionException extends JedisException {
/**
* Creates connection exception
* @param message Connection error message
*/
public JedisConnectionException(String message);
/**
* Creates connection exception with cause
* @param message Error message
* @param cause Network or I/O exception cause
*/
public JedisConnectionException(String message, Throwable cause);
/**
* Creates connection exception from cause
* @param cause Network exception
*/
public JedisConnectionException(Throwable cause);
}public void handleConnectionErrors() {
Jedis jedis = null;
try {
jedis = new Jedis("redis.example.com", 6379);
jedis.set("key", "value");
} catch (JedisConnectionException e) {
System.err.println("Connection failed: " + e.getMessage());
// Check underlying cause
Throwable cause = e.getCause();
if (cause instanceof SocketTimeoutException) {
System.err.println("Connection timed out");
} else if (cause instanceof ConnectException) {
System.err.println("Cannot reach Redis server");
} else if (cause instanceof UnknownHostException) {
System.err.println("Invalid Redis hostname");
}
// Implement retry logic
retryConnection();
} finally {
if (jedis != null) {
try {
jedis.close();
} catch (Exception e) {
System.err.println("Error closing connection: " + e.getMessage());
}
}
}
}
private void retryConnection() {
int maxRetries = 3;
int retryDelay = 1000; // 1 second
for (int i = 0; i < maxRetries; i++) {
try {
Thread.sleep(retryDelay * (i + 1)); // Exponential backoff
Jedis jedis = new Jedis("redis.example.com", 6379);
jedis.ping(); // Test connection
jedis.close();
System.out.println("Connection restored");
return;
} catch (JedisConnectionException | InterruptedException e) {
System.err.println("Retry " + (i + 1) + " failed: " + e.getMessage());
}
}
System.err.println("All retry attempts failed");
}General Redis Cluster operation exceptions.
public class JedisClusterException extends JedisException {
/**
* Creates cluster exception
* @param message Error message
*/
public JedisClusterException(String message);
/**
* Creates cluster exception with cause
* @param message Error message
* @param cause Exception cause
*/
public JedisClusterException(String message, Throwable cause);
/**
* Creates cluster exception from cause
* @param cause Exception cause
*/
public JedisClusterException(Throwable cause);
}Exceptions specific to cluster operations.
public class JedisClusterOperationException extends JedisClusterException {
/**
* Creates cluster operation exception
* @param message Operation error message
*/
public JedisClusterOperationException(String message);
/**
* Creates cluster operation exception with cause
* @param message Error message
* @param cause Exception cause
*/
public JedisClusterOperationException(String message, Throwable cause);
}Base class for Redis Cluster redirection responses.
public abstract class JedisRedirectionException extends JedisException {
/**
* Creates redirection exception
* @param message Redirection message
* @param targetNode Target node for redirection
* @param slot Cluster slot number
*/
public JedisRedirectionException(String message, HostAndPort targetNode, int slot);
/**
* Gets target node for redirection
* @return Target Redis cluster node
*/
public HostAndPort getTargetNode();
/**
* Gets cluster slot number
* @return Slot number (0-16383)
*/
public int getSlot();
}Exception for Redis Cluster MOVED responses.
public class JedisMovedDataException extends JedisRedirectionException {
/**
* Creates MOVED exception
* @param message MOVED response message
* @param targetNode Node that owns the slot
* @param slot Cluster slot number
*/
public JedisMovedDataException(String message, HostAndPort targetNode, int slot);
}Exception for Redis Cluster ASK responses.
public class JedisAskDataException extends JedisRedirectionException {
/**
* Creates ASK exception
* @param message ASK response message
* @param targetNode Node to ask for data
* @param slot Cluster slot number
*/
public JedisAskDataException(String message, HostAndPort targetNode, int slot);
}public class ClusterErrorHandler {
private static final int MAX_REDIRECTIONS = 5;
public String getWithRedirection(JedisCluster cluster, String key) {
return executeWithRedirection(() -> cluster.get(key));
}
public <T> T executeWithRedirection(Supplier<T> operation) {
int redirections = 0;
while (redirections < MAX_REDIRECTIONS) {
try {
return operation.get();
} catch (JedisMovedDataException e) {
// Cluster topology changed, refresh slot mapping
System.out.println("MOVED: Slot " + e.getSlot() +
" moved to " + e.getTargetNode());
// JedisCluster handles this automatically, but we log it
redirections++;
if (redirections >= MAX_REDIRECTIONS) {
throw new JedisClusterOperationException(
"Too many MOVED redirections", e);
}
} catch (JedisAskDataException e) {
// Temporary redirection during slot migration
System.out.println("ASK: Slot " + e.getSlot() +
" temporarily at " + e.getTargetNode());
// JedisCluster handles this automatically
redirections++;
if (redirections >= MAX_REDIRECTIONS) {
throw new JedisClusterOperationException(
"Too many ASK redirections", e);
}
} catch (JedisClusterException e) {
// General cluster error
System.err.println("Cluster operation failed: " + e.getMessage());
if (e.getCause() instanceof JedisConnectionException) {
// Node unavailable, cluster may retry automatically
throw new JedisClusterOperationException(
"Cluster node unavailable", e);
}
throw e;
}
}
throw new JedisClusterOperationException("Max redirections exceeded");
}
}Exception when Redis server is busy.
public class JedisBusyException extends JedisDataException {
/**
* Creates busy exception
* @param message Server busy message
*/
public JedisBusyException(String message);
}Exception when Lua script is not found in server cache.
public class JedisNoScriptException extends JedisDataException {
/**
* Creates no-script exception
* @param message Script not found message
*/
public JedisNoScriptException(String message);
/**
* Gets script SHA that wasn't found
* @return Script SHA hash
*/
public String getScriptSha();
}public class ScriptManager {
private final Map<String, String> scriptShas = new ConcurrentHashMap<>();
public Object executeScript(Jedis jedis, String script, List<String> keys,
List<String> args) {
String sha = scriptShas.computeIfAbsent(script,
s -> jedis.scriptLoad(s));
try {
return jedis.evalsha(sha, keys, args);
} catch (JedisNoScriptException e) {
System.out.println("Script not in cache, loading: " + e.getScriptSha());
// Reload script and retry
String newSha = jedis.scriptLoad(script);
scriptShas.put(script, newSha);
return jedis.evalsha(newSha, keys, args);
} catch (JedisBusyException e) {
System.err.println("Redis server busy: " + e.getMessage());
// Wait and retry once
try {
Thread.sleep(100);
return jedis.evalsha(sha, keys, args);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new JedisException("Script execution interrupted", ie);
}
}
}
}Exception for Redis ACL (Access Control List) violations.
public class JedisAccessControlException extends JedisDataException {
/**
* Creates ACL exception
* @param message Access control error message
*/
public JedisAccessControlException(String message);
}Exception for authentication failures.
public class JedisAuthenticationException extends JedisException {
/**
* Creates authentication exception
* @param message Authentication error message
*/
public JedisAuthenticationException(String message);
/**
* Creates authentication exception with cause
* @param message Error message
* @param cause Exception cause
*/
public JedisAuthenticationException(String message, Throwable cause);
}public class SecureRedisClient {
private final String username;
private final String password;
public SecureRedisClient(String username, String password) {
this.username = username;
this.password = password;
}
public Jedis createSecureConnection(String host, int port) {
try {
Jedis jedis = new Jedis(host, port);
// Authenticate
if (username != null && password != null) {
jedis.auth(username, password);
} else if (password != null) {
jedis.auth(password);
}
// Test connection
jedis.ping();
return jedis;
} catch (JedisAuthenticationException e) {
System.err.println("Authentication failed: " + e.getMessage());
throw new SecurityException("Cannot authenticate to Redis", e);
} catch (JedisAccessControlException e) {
System.err.println("Access denied: " + e.getMessage());
if (e.getMessage().contains("NOPERM")) {
System.err.println("User lacks required permissions");
} else if (e.getMessage().contains("WRONGPASS")) {
System.err.println("Invalid password");
}
throw new SecurityException("Access control violation", e);
} catch (JedisConnectionException e) {
System.err.println("Connection failed during authentication: " + e.getMessage());
throw e;
}
}
public <T> T executeSecureOperation(String host, int port,
Function<Jedis, T> operation) {
Jedis jedis = null;
try {
jedis = createSecureConnection(host, port);
return operation.apply(jedis);
} catch (JedisAccessControlException e) {
System.err.println("Operation denied by ACL: " + e.getMessage());
// Could implement fallback to read-only operations
if (e.getMessage().contains("WRITE")) {
System.out.println("Attempting read-only fallback");
// Implement read-only version
}
throw e;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}Exception for input validation errors.
public class JedisValidationException extends JedisException {
/**
* Creates validation exception
* @param message Validation error message
*/
public JedisValidationException(String message);
}Exception for malformed Redis connection URIs.
public class InvalidURIException extends JedisException {
/**
* Creates invalid URI exception
* @param message URI error message
*/
public InvalidURIException(String message);
/**
* Creates invalid URI exception with cause
* @param message Error message
* @param cause URI parsing exception
*/
public InvalidURIException(String message, Throwable cause);
}Exception for client-side cache operations.
public class JedisCacheException extends JedisException {
/**
* Creates cache exception
* @param message Cache error message
*/
public JedisCacheException(String message);
/**
* Creates cache exception with cause
* @param message Error message
* @param cause Cache operation cause
*/
public JedisCacheException(String message, Throwable cause);
}Exception for broadcast operations across multiple Redis instances.
public class JedisBroadcastException extends JedisException {
/**
* Creates broadcast exception
* @param message Broadcast error message
*/
public JedisBroadcastException(String message);
/**
* Creates broadcast exception with partial results
* @param message Error message
* @param cause Exception cause
*/
public JedisBroadcastException(String message, Throwable cause);
}public class RobustJedisClient {
private static final Logger logger = LoggerFactory.getLogger(RobustJedisClient.class);
private final JedisPool pool;
private final int maxRetries;
public RobustJedisClient(JedisPool pool, int maxRetries) {
this.pool = pool;
this.maxRetries = maxRetries;
}
public <T> T executeWithRetry(Function<Jedis, T> operation) {
int attempts = 0;
JedisException lastException = null;
while (attempts < maxRetries) {
try (Jedis jedis = pool.getResource()) {
return operation.apply(jedis);
} catch (JedisConnectionException e) {
lastException = e;
attempts++;
logger.warn("Connection attempt {} failed: {}", attempts, e.getMessage());
if (attempts < maxRetries) {
waitBeforeRetry(attempts);
}
} catch (JedisBusyException e) {
lastException = e;
attempts++;
logger.warn("Server busy on attempt {}: {}", attempts, e.getMessage());
if (attempts < maxRetries) {
waitBeforeRetry(attempts);
}
} catch (JedisDataException e) {
// Data exceptions usually don't benefit from retry
logger.error("Data operation failed: {}", e.getMessage());
throw e;
} catch (JedisAuthenticationException e) {
// Authentication errors shouldn't be retried
logger.error("Authentication failed: {}", e.getMessage());
throw e;
} catch (JedisValidationException e) {
// Validation errors shouldn't be retried
logger.error("Validation failed: {}", e.getMessage());
throw e;
} catch (JedisException e) {
// Generic Jedis exception
lastException = e;
attempts++;
logger.warn("Operation attempt {} failed: {}", attempts, e.getMessage());
if (attempts < maxRetries) {
waitBeforeRetry(attempts);
}
}
}
logger.error("All {} retry attempts failed", maxRetries);
throw new JedisException("Operation failed after " + maxRetries + " attempts",
lastException);
}
private void waitBeforeRetry(int attempt) {
try {
// Exponential backoff with jitter
long delay = Math.min(1000 * (1L << attempt), 10000); // Max 10 seconds
long jitter = (long) (Math.random() * 1000); // Up to 1 second jitter
Thread.sleep(delay + jitter);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new JedisException("Retry interrupted", e);
}
}
}public class CircuitBreakerJedisClient {
private enum State { CLOSED, OPEN, HALF_OPEN }
private volatile State state = State.CLOSED;
private volatile int failures = 0;
private volatile long lastFailureTime = 0;
private final int failureThreshold;
private final long timeout;
public CircuitBreakerJedisClient(int failureThreshold, long timeout) {
this.failureThreshold = failureThreshold;
this.timeout = timeout;
}
public <T> T execute(Function<Jedis, T> operation) {
if (state == State.OPEN) {
if (System.currentTimeMillis() - lastFailureTime > timeout) {
state = State.HALF_OPEN;
} else {
throw new JedisException("Circuit breaker is OPEN");
}
}
try (Jedis jedis = getJedis()) {
T result = operation.apply(jedis);
onSuccess();
return result;
} catch (JedisException e) {
onFailure();
throw e;
}
}
private synchronized void onSuccess() {
failures = 0;
state = State.CLOSED;
}
private synchronized void onFailure() {
failures++;
lastFailureTime = System.currentTimeMillis();
if (failures >= failureThreshold) {
state = State.OPEN;
}
}
private Jedis getJedis() {
// Get Jedis instance from pool or create new one
return new Jedis("localhost", 6379);
}
}public class MonitoredJedisClient {
private static final Logger logger = LoggerFactory.getLogger(MonitoredJedisClient.class);
private final MeterRegistry meterRegistry;
public MonitoredJedisClient(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
public <T> T executeWithMonitoring(String operationName, Function<Jedis, T> operation) {
Timer.Sample sample = Timer.start(meterRegistry);
try (Jedis jedis = new Jedis("localhost", 6379)) {
T result = operation.apply(jedis);
meterRegistry.counter("jedis.operations.success", "operation", operationName)
.increment();
return result;
} catch (JedisConnectionException e) {
logAndCount("connection_error", operationName, e);
throw e;
} catch (JedisClusterException e) {
logAndCount("cluster_error", operationName, e);
throw e;
} catch (JedisAuthenticationException e) {
logAndCount("auth_error", operationName, e);
throw e;
} catch (JedisDataException e) {
logAndCount("data_error", operationName, e);
throw e;
} catch (JedisException e) {
logAndCount("generic_error", operationName, e);
throw e;
} finally {
sample.stop(Timer.builder("jedis.operations.duration")
.tag("operation", operationName)
.register(meterRegistry));
}
}
private void logAndCount(String errorType, String operationName, JedisException e) {
logger.error("Jedis {} error in operation {}: {}",
errorType, operationName, e.getMessage(), e);
meterRegistry.counter("jedis.operations.error",
"operation", operationName,
"error_type", errorType)
.increment();
}
}The Jedis exception hierarchy provides comprehensive error handling capabilities for all Redis deployment scenarios. Proper exception handling is crucial for building resilient applications that can gracefully handle network issues, cluster topology changes, authentication problems, and server errors.
Install with Tessl CLI
npx tessl i tessl/maven-redis-clients--jedis