0
# Exception Handling
1
2
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.
3
4
## Exception Hierarchy
5
6
### Base Exception Classes
7
8
#### JedisException
9
10
Root exception class for all Jedis-specific exceptions.
11
12
```java { .api }
13
public class JedisException extends RuntimeException {
14
/**
15
* Creates exception with message
16
* @param message Error message
17
*/
18
public JedisException(String message);
19
20
/**
21
* Creates exception with message and cause
22
* @param message Error message
23
* @param cause Underlying cause
24
*/
25
public JedisException(String message, Throwable cause);
26
27
/**
28
* Creates exception with cause
29
* @param cause Underlying cause
30
*/
31
public JedisException(Throwable cause);
32
33
/**
34
* Gets error message
35
* @return Exception message
36
*/
37
@Override
38
public String getMessage();
39
40
/**
41
* Gets underlying cause
42
* @return Exception cause
43
*/
44
@Override
45
public Throwable getCause();
46
}
47
```
48
49
#### JedisDataException
50
51
Exception for Redis server errors and data-related issues.
52
53
```java { .api }
54
public class JedisDataException extends JedisException {
55
/**
56
* Creates data exception with message
57
* @param message Error message from Redis server
58
*/
59
public JedisDataException(String message);
60
61
/**
62
* Creates data exception with message and cause
63
* @param message Error message
64
* @param cause Underlying cause
65
*/
66
public JedisDataException(String message, Throwable cause);
67
}
68
```
69
70
## Connection Exceptions
71
72
### JedisConnectionException
73
74
Exception for connection-related failures.
75
76
```java { .api }
77
public class JedisConnectionException extends JedisException {
78
/**
79
* Creates connection exception
80
* @param message Connection error message
81
*/
82
public JedisConnectionException(String message);
83
84
/**
85
* Creates connection exception with cause
86
* @param message Error message
87
* @param cause Network or I/O exception cause
88
*/
89
public JedisConnectionException(String message, Throwable cause);
90
91
/**
92
* Creates connection exception from cause
93
* @param cause Network exception
94
*/
95
public JedisConnectionException(Throwable cause);
96
}
97
```
98
99
#### Usage Example
100
101
```java
102
public void handleConnectionErrors() {
103
Jedis jedis = null;
104
try {
105
jedis = new Jedis("redis.example.com", 6379);
106
jedis.set("key", "value");
107
108
} catch (JedisConnectionException e) {
109
System.err.println("Connection failed: " + e.getMessage());
110
111
// Check underlying cause
112
Throwable cause = e.getCause();
113
if (cause instanceof SocketTimeoutException) {
114
System.err.println("Connection timed out");
115
} else if (cause instanceof ConnectException) {
116
System.err.println("Cannot reach Redis server");
117
} else if (cause instanceof UnknownHostException) {
118
System.err.println("Invalid Redis hostname");
119
}
120
121
// Implement retry logic
122
retryConnection();
123
124
} finally {
125
if (jedis != null) {
126
try {
127
jedis.close();
128
} catch (Exception e) {
129
System.err.println("Error closing connection: " + e.getMessage());
130
}
131
}
132
}
133
}
134
135
private void retryConnection() {
136
int maxRetries = 3;
137
int retryDelay = 1000; // 1 second
138
139
for (int i = 0; i < maxRetries; i++) {
140
try {
141
Thread.sleep(retryDelay * (i + 1)); // Exponential backoff
142
143
Jedis jedis = new Jedis("redis.example.com", 6379);
144
jedis.ping(); // Test connection
145
jedis.close();
146
147
System.out.println("Connection restored");
148
return;
149
150
} catch (JedisConnectionException | InterruptedException e) {
151
System.err.println("Retry " + (i + 1) + " failed: " + e.getMessage());
152
}
153
}
154
155
System.err.println("All retry attempts failed");
156
}
157
```
158
159
## Cluster Exceptions
160
161
### JedisClusterException
162
163
General Redis Cluster operation exceptions.
164
165
```java { .api }
166
public class JedisClusterException extends JedisException {
167
/**
168
* Creates cluster exception
169
* @param message Error message
170
*/
171
public JedisClusterException(String message);
172
173
/**
174
* Creates cluster exception with cause
175
* @param message Error message
176
* @param cause Exception cause
177
*/
178
public JedisClusterException(String message, Throwable cause);
179
180
/**
181
* Creates cluster exception from cause
182
* @param cause Exception cause
183
*/
184
public JedisClusterException(Throwable cause);
185
}
186
```
187
188
### JedisClusterOperationException
189
190
Exceptions specific to cluster operations.
191
192
```java { .api }
193
public class JedisClusterOperationException extends JedisClusterException {
194
/**
195
* Creates cluster operation exception
196
* @param message Operation error message
197
*/
198
public JedisClusterOperationException(String message);
199
200
/**
201
* Creates cluster operation exception with cause
202
* @param message Error message
203
* @param cause Exception cause
204
*/
205
public JedisClusterOperationException(String message, Throwable cause);
206
}
207
```
208
209
### Cluster Redirection Exceptions
210
211
#### JedisRedirectionException
212
213
Base class for Redis Cluster redirection responses.
214
215
```java { .api }
216
public abstract class JedisRedirectionException extends JedisException {
217
/**
218
* Creates redirection exception
219
* @param message Redirection message
220
* @param targetNode Target node for redirection
221
* @param slot Cluster slot number
222
*/
223
public JedisRedirectionException(String message, HostAndPort targetNode, int slot);
224
225
/**
226
* Gets target node for redirection
227
* @return Target Redis cluster node
228
*/
229
public HostAndPort getTargetNode();
230
231
/**
232
* Gets cluster slot number
233
* @return Slot number (0-16383)
234
*/
235
public int getSlot();
236
}
237
```
238
239
#### JedisMovedDataException
240
241
Exception for Redis Cluster MOVED responses.
242
243
```java { .api }
244
public class JedisMovedDataException extends JedisRedirectionException {
245
/**
246
* Creates MOVED exception
247
* @param message MOVED response message
248
* @param targetNode Node that owns the slot
249
* @param slot Cluster slot number
250
*/
251
public JedisMovedDataException(String message, HostAndPort targetNode, int slot);
252
}
253
```
254
255
#### JedisAskDataException
256
257
Exception for Redis Cluster ASK responses.
258
259
```java { .api }
260
public class JedisAskDataException extends JedisRedirectionException {
261
/**
262
* Creates ASK exception
263
* @param message ASK response message
264
* @param targetNode Node to ask for data
265
* @param slot Cluster slot number
266
*/
267
public JedisAskDataException(String message, HostAndPort targetNode, int slot);
268
}
269
```
270
271
#### Cluster Exception Handling Example
272
273
```java
274
public class ClusterErrorHandler {
275
private static final int MAX_REDIRECTIONS = 5;
276
277
public String getWithRedirection(JedisCluster cluster, String key) {
278
return executeWithRedirection(() -> cluster.get(key));
279
}
280
281
public <T> T executeWithRedirection(Supplier<T> operation) {
282
int redirections = 0;
283
284
while (redirections < MAX_REDIRECTIONS) {
285
try {
286
return operation.get();
287
288
} catch (JedisMovedDataException e) {
289
// Cluster topology changed, refresh slot mapping
290
System.out.println("MOVED: Slot " + e.getSlot() +
291
" moved to " + e.getTargetNode());
292
293
// JedisCluster handles this automatically, but we log it
294
redirections++;
295
296
if (redirections >= MAX_REDIRECTIONS) {
297
throw new JedisClusterOperationException(
298
"Too many MOVED redirections", e);
299
}
300
301
} catch (JedisAskDataException e) {
302
// Temporary redirection during slot migration
303
System.out.println("ASK: Slot " + e.getSlot() +
304
" temporarily at " + e.getTargetNode());
305
306
// JedisCluster handles this automatically
307
redirections++;
308
309
if (redirections >= MAX_REDIRECTIONS) {
310
throw new JedisClusterOperationException(
311
"Too many ASK redirections", e);
312
}
313
314
} catch (JedisClusterException e) {
315
// General cluster error
316
System.err.println("Cluster operation failed: " + e.getMessage());
317
318
if (e.getCause() instanceof JedisConnectionException) {
319
// Node unavailable, cluster may retry automatically
320
throw new JedisClusterOperationException(
321
"Cluster node unavailable", e);
322
}
323
324
throw e;
325
}
326
}
327
328
throw new JedisClusterOperationException("Max redirections exceeded");
329
}
330
}
331
```
332
333
## Server State Exceptions
334
335
### JedisBusyException
336
337
Exception when Redis server is busy.
338
339
```java { .api }
340
public class JedisBusyException extends JedisDataException {
341
/**
342
* Creates busy exception
343
* @param message Server busy message
344
*/
345
public JedisBusyException(String message);
346
}
347
```
348
349
### JedisNoScriptException
350
351
Exception when Lua script is not found in server cache.
352
353
```java { .api }
354
public class JedisNoScriptException extends JedisDataException {
355
/**
356
* Creates no-script exception
357
* @param message Script not found message
358
*/
359
public JedisNoScriptException(String message);
360
361
/**
362
* Gets script SHA that wasn't found
363
* @return Script SHA hash
364
*/
365
public String getScriptSha();
366
}
367
```
368
369
#### Server State Exception Handling
370
371
```java
372
public class ScriptManager {
373
private final Map<String, String> scriptShas = new ConcurrentHashMap<>();
374
375
public Object executeScript(Jedis jedis, String script, List<String> keys,
376
List<String> args) {
377
String sha = scriptShas.computeIfAbsent(script,
378
s -> jedis.scriptLoad(s));
379
380
try {
381
return jedis.evalsha(sha, keys, args);
382
383
} catch (JedisNoScriptException e) {
384
System.out.println("Script not in cache, loading: " + e.getScriptSha());
385
386
// Reload script and retry
387
String newSha = jedis.scriptLoad(script);
388
scriptShas.put(script, newSha);
389
390
return jedis.evalsha(newSha, keys, args);
391
392
} catch (JedisBusyException e) {
393
System.err.println("Redis server busy: " + e.getMessage());
394
395
// Wait and retry once
396
try {
397
Thread.sleep(100);
398
return jedis.evalsha(sha, keys, args);
399
} catch (InterruptedException ie) {
400
Thread.currentThread().interrupt();
401
throw new JedisException("Script execution interrupted", ie);
402
}
403
}
404
}
405
}
406
```
407
408
## Security Exceptions
409
410
### Authentication Exceptions
411
412
#### JedisAccessControlException
413
414
Exception for Redis ACL (Access Control List) violations.
415
416
```java { .api }
417
public class JedisAccessControlException extends JedisDataException {
418
/**
419
* Creates ACL exception
420
* @param message Access control error message
421
*/
422
public JedisAccessControlException(String message);
423
}
424
```
425
426
#### JedisAuthenticationException
427
428
Exception for authentication failures.
429
430
```java { .api }
431
public class JedisAuthenticationException extends JedisException {
432
/**
433
* Creates authentication exception
434
* @param message Authentication error message
435
*/
436
public JedisAuthenticationException(String message);
437
438
/**
439
* Creates authentication exception with cause
440
* @param message Error message
441
* @param cause Exception cause
442
*/
443
public JedisAuthenticationException(String message, Throwable cause);
444
}
445
```
446
447
#### Security Exception Handling
448
449
```java
450
public class SecureRedisClient {
451
private final String username;
452
private final String password;
453
454
public SecureRedisClient(String username, String password) {
455
this.username = username;
456
this.password = password;
457
}
458
459
public Jedis createSecureConnection(String host, int port) {
460
try {
461
Jedis jedis = new Jedis(host, port);
462
463
// Authenticate
464
if (username != null && password != null) {
465
jedis.auth(username, password);
466
} else if (password != null) {
467
jedis.auth(password);
468
}
469
470
// Test connection
471
jedis.ping();
472
return jedis;
473
474
} catch (JedisAuthenticationException e) {
475
System.err.println("Authentication failed: " + e.getMessage());
476
throw new SecurityException("Cannot authenticate to Redis", e);
477
478
} catch (JedisAccessControlException e) {
479
System.err.println("Access denied: " + e.getMessage());
480
481
if (e.getMessage().contains("NOPERM")) {
482
System.err.println("User lacks required permissions");
483
} else if (e.getMessage().contains("WRONGPASS")) {
484
System.err.println("Invalid password");
485
}
486
487
throw new SecurityException("Access control violation", e);
488
489
} catch (JedisConnectionException e) {
490
System.err.println("Connection failed during authentication: " + e.getMessage());
491
throw e;
492
}
493
}
494
495
public <T> T executeSecureOperation(String host, int port,
496
Function<Jedis, T> operation) {
497
Jedis jedis = null;
498
try {
499
jedis = createSecureConnection(host, port);
500
return operation.apply(jedis);
501
502
} catch (JedisAccessControlException e) {
503
System.err.println("Operation denied by ACL: " + e.getMessage());
504
505
// Could implement fallback to read-only operations
506
if (e.getMessage().contains("WRITE")) {
507
System.out.println("Attempting read-only fallback");
508
// Implement read-only version
509
}
510
511
throw e;
512
513
} finally {
514
if (jedis != null) {
515
jedis.close();
516
}
517
}
518
}
519
}
520
```
521
522
## Validation Exceptions
523
524
### JedisValidationException
525
526
Exception for input validation errors.
527
528
```java { .api }
529
public class JedisValidationException extends JedisException {
530
/**
531
* Creates validation exception
532
* @param message Validation error message
533
*/
534
public JedisValidationException(String message);
535
}
536
```
537
538
### InvalidURIException
539
540
Exception for malformed Redis connection URIs.
541
542
```java { .api }
543
public class InvalidURIException extends JedisException {
544
/**
545
* Creates invalid URI exception
546
* @param message URI error message
547
*/
548
public InvalidURIException(String message);
549
550
/**
551
* Creates invalid URI exception with cause
552
* @param message Error message
553
* @param cause URI parsing exception
554
*/
555
public InvalidURIException(String message, Throwable cause);
556
}
557
```
558
559
## Cache and Broadcast Exceptions
560
561
### JedisCacheException
562
563
Exception for client-side cache operations.
564
565
```java { .api }
566
public class JedisCacheException extends JedisException {
567
/**
568
* Creates cache exception
569
* @param message Cache error message
570
*/
571
public JedisCacheException(String message);
572
573
/**
574
* Creates cache exception with cause
575
* @param message Error message
576
* @param cause Cache operation cause
577
*/
578
public JedisCacheException(String message, Throwable cause);
579
}
580
```
581
582
### JedisBroadcastException
583
584
Exception for broadcast operations across multiple Redis instances.
585
586
```java { .api }
587
public class JedisBroadcastException extends JedisException {
588
/**
589
* Creates broadcast exception
590
* @param message Broadcast error message
591
*/
592
public JedisBroadcastException(String message);
593
594
/**
595
* Creates broadcast exception with partial results
596
* @param message Error message
597
* @param cause Exception cause
598
*/
599
public JedisBroadcastException(String message, Throwable cause);
600
}
601
```
602
603
## Exception Handling Best Practices
604
605
### Comprehensive Error Handler
606
607
```java
608
public class RobustJedisClient {
609
private static final Logger logger = LoggerFactory.getLogger(RobustJedisClient.class);
610
private final JedisPool pool;
611
private final int maxRetries;
612
613
public RobustJedisClient(JedisPool pool, int maxRetries) {
614
this.pool = pool;
615
this.maxRetries = maxRetries;
616
}
617
618
public <T> T executeWithRetry(Function<Jedis, T> operation) {
619
int attempts = 0;
620
JedisException lastException = null;
621
622
while (attempts < maxRetries) {
623
try (Jedis jedis = pool.getResource()) {
624
return operation.apply(jedis);
625
626
} catch (JedisConnectionException e) {
627
lastException = e;
628
attempts++;
629
630
logger.warn("Connection attempt {} failed: {}", attempts, e.getMessage());
631
632
if (attempts < maxRetries) {
633
waitBeforeRetry(attempts);
634
}
635
636
} catch (JedisBusyException e) {
637
lastException = e;
638
attempts++;
639
640
logger.warn("Server busy on attempt {}: {}", attempts, e.getMessage());
641
642
if (attempts < maxRetries) {
643
waitBeforeRetry(attempts);
644
}
645
646
} catch (JedisDataException e) {
647
// Data exceptions usually don't benefit from retry
648
logger.error("Data operation failed: {}", e.getMessage());
649
throw e;
650
651
} catch (JedisAuthenticationException e) {
652
// Authentication errors shouldn't be retried
653
logger.error("Authentication failed: {}", e.getMessage());
654
throw e;
655
656
} catch (JedisValidationException e) {
657
// Validation errors shouldn't be retried
658
logger.error("Validation failed: {}", e.getMessage());
659
throw e;
660
661
} catch (JedisException e) {
662
// Generic Jedis exception
663
lastException = e;
664
attempts++;
665
666
logger.warn("Operation attempt {} failed: {}", attempts, e.getMessage());
667
668
if (attempts < maxRetries) {
669
waitBeforeRetry(attempts);
670
}
671
}
672
}
673
674
logger.error("All {} retry attempts failed", maxRetries);
675
throw new JedisException("Operation failed after " + maxRetries + " attempts",
676
lastException);
677
}
678
679
private void waitBeforeRetry(int attempt) {
680
try {
681
// Exponential backoff with jitter
682
long delay = Math.min(1000 * (1L << attempt), 10000); // Max 10 seconds
683
long jitter = (long) (Math.random() * 1000); // Up to 1 second jitter
684
685
Thread.sleep(delay + jitter);
686
} catch (InterruptedException e) {
687
Thread.currentThread().interrupt();
688
throw new JedisException("Retry interrupted", e);
689
}
690
}
691
}
692
```
693
694
### Circuit Breaker Pattern
695
696
```java
697
public class CircuitBreakerJedisClient {
698
private enum State { CLOSED, OPEN, HALF_OPEN }
699
700
private volatile State state = State.CLOSED;
701
private volatile int failures = 0;
702
private volatile long lastFailureTime = 0;
703
private final int failureThreshold;
704
private final long timeout;
705
706
public CircuitBreakerJedisClient(int failureThreshold, long timeout) {
707
this.failureThreshold = failureThreshold;
708
this.timeout = timeout;
709
}
710
711
public <T> T execute(Function<Jedis, T> operation) {
712
if (state == State.OPEN) {
713
if (System.currentTimeMillis() - lastFailureTime > timeout) {
714
state = State.HALF_OPEN;
715
} else {
716
throw new JedisException("Circuit breaker is OPEN");
717
}
718
}
719
720
try (Jedis jedis = getJedis()) {
721
T result = operation.apply(jedis);
722
onSuccess();
723
return result;
724
725
} catch (JedisException e) {
726
onFailure();
727
throw e;
728
}
729
}
730
731
private synchronized void onSuccess() {
732
failures = 0;
733
state = State.CLOSED;
734
}
735
736
private synchronized void onFailure() {
737
failures++;
738
lastFailureTime = System.currentTimeMillis();
739
740
if (failures >= failureThreshold) {
741
state = State.OPEN;
742
}
743
}
744
745
private Jedis getJedis() {
746
// Get Jedis instance from pool or create new one
747
return new Jedis("localhost", 6379);
748
}
749
}
750
```
751
752
### Exception Logging and Monitoring
753
754
```java
755
public class MonitoredJedisClient {
756
private static final Logger logger = LoggerFactory.getLogger(MonitoredJedisClient.class);
757
private final MeterRegistry meterRegistry;
758
759
public MonitoredJedisClient(MeterRegistry meterRegistry) {
760
this.meterRegistry = meterRegistry;
761
}
762
763
public <T> T executeWithMonitoring(String operationName, Function<Jedis, T> operation) {
764
Timer.Sample sample = Timer.start(meterRegistry);
765
766
try (Jedis jedis = new Jedis("localhost", 6379)) {
767
T result = operation.apply(jedis);
768
769
meterRegistry.counter("jedis.operations.success", "operation", operationName)
770
.increment();
771
772
return result;
773
774
} catch (JedisConnectionException e) {
775
logAndCount("connection_error", operationName, e);
776
throw e;
777
778
} catch (JedisClusterException e) {
779
logAndCount("cluster_error", operationName, e);
780
throw e;
781
782
} catch (JedisAuthenticationException e) {
783
logAndCount("auth_error", operationName, e);
784
throw e;
785
786
} catch (JedisDataException e) {
787
logAndCount("data_error", operationName, e);
788
throw e;
789
790
} catch (JedisException e) {
791
logAndCount("generic_error", operationName, e);
792
throw e;
793
794
} finally {
795
sample.stop(Timer.builder("jedis.operations.duration")
796
.tag("operation", operationName)
797
.register(meterRegistry));
798
}
799
}
800
801
private void logAndCount(String errorType, String operationName, JedisException e) {
802
logger.error("Jedis {} error in operation {}: {}",
803
errorType, operationName, e.getMessage(), e);
804
805
meterRegistry.counter("jedis.operations.error",
806
"operation", operationName,
807
"error_type", errorType)
808
.increment();
809
}
810
}
811
```
812
813
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.