0
# Exception Utilities
1
2
Apache Commons Lang provides comprehensive exception handling utilities through ExceptionUtils and contextual exception classes. These utilities offer stack trace analysis, cause extraction, exception chaining, and enhanced error reporting capabilities.
3
4
## Core Exception Classes
5
6
### ExceptionUtils - Exception Analysis and Manipulation
7
8
Provides 35 static methods for analyzing exception hierarchies, extracting stack traces, and handling exception chains:
9
10
```java { .api }
11
import org.apache.commons.lang3.exception.ExceptionUtils;
12
```
13
14
#### Stack Trace Operations
15
16
```java { .api }
17
// Stack trace extraction
18
public static String getStackTrace(Throwable throwable)
19
public static String[] getStackFrames(Throwable throwable)
20
public static List<String> getStackFrameList(Throwable throwable)
21
22
// Root cause stack traces
23
public static String[] getRootCauseStackTrace(Throwable throwable)
24
public static List<String> getRootCauseStackTraceList(Throwable throwable)
25
```
26
27
**Usage Examples:**
28
```java { .api }
29
public class StackTraceExamples {
30
31
public void demonstrateStackTraceOperations() {
32
try {
33
riskyOperation();
34
} catch (Exception e) {
35
// Get complete stack trace as string
36
String fullStackTrace = ExceptionUtils.getStackTrace(e);
37
log.error("Full stack trace:\n{}", fullStackTrace);
38
39
// Get stack trace as array of frames
40
String[] frames = ExceptionUtils.getStackFrames(e);
41
for (int i = 0; i < Math.min(5, frames.length); i++) {
42
log.debug("Frame {}: {}", i, frames[i]);
43
}
44
45
// Get root cause stack trace (if exception has causes)
46
String[] rootFrames = ExceptionUtils.getRootCauseStackTrace(e);
47
log.error("Root cause stack trace has {} frames", rootFrames.length);
48
}
49
}
50
51
// Custom logging with stack trace details
52
public void logExceptionDetails(Exception e) {
53
StringBuilder details = new StringBuilder();
54
details.append("Exception Details:\n");
55
details.append("Type: ").append(e.getClass().getSimpleName()).append("\n");
56
details.append("Message: ").append(e.getMessage()).append("\n");
57
58
// Add stack trace information
59
List<String> stackFrames = ExceptionUtils.getStackFrameList(e);
60
details.append("Stack frames (top 5):\n");
61
62
for (int i = 0; i < Math.min(5, stackFrames.size()); i++) {
63
details.append(" ").append(i + 1).append(". ").append(stackFrames.get(i)).append("\n");
64
}
65
66
log.error(details.toString());
67
}
68
}
69
```
70
71
#### Cause Chain Analysis
72
73
```java { .api }
74
// Root cause extraction
75
public static Throwable getRootCause(Throwable throwable)
76
public static String getRootCauseMessage(Throwable throwable)
77
78
// Cause chain navigation
79
public static Throwable getCause(Throwable throwable)
80
public static Throwable getCause(Throwable throwable, String[] methodNames)
81
public static int getThrowableCount(Throwable throwable)
82
public static Throwable[] getThrowables(Throwable throwable)
83
public static List<Throwable> getThrowableList(Throwable throwable)
84
```
85
86
**Usage Examples:**
87
```java { .api }
88
public class CauseChainExamples {
89
90
public void analyzeCauseChain(Exception exception) {
91
// Get root cause
92
Throwable rootCause = ExceptionUtils.getRootCause(exception);
93
if (rootCause != null) {
94
log.error("Root cause: {} - {}", rootCause.getClass().getSimpleName(), rootCause.getMessage());
95
}
96
97
// Get root cause message (handles null safely)
98
String rootMessage = ExceptionUtils.getRootCauseMessage(exception);
99
log.info("Root cause message: {}", rootMessage);
100
101
// Analyze complete cause chain
102
List<Throwable> throwableChain = ExceptionUtils.getThrowableList(exception);
103
log.info("Exception chain has {} levels:", throwableChain.size());
104
105
for (int i = 0; i < throwableChain.size(); i++) {
106
Throwable t = throwableChain.get(i);
107
log.info(" Level {}: {} - {}", i, t.getClass().getSimpleName(), t.getMessage());
108
}
109
}
110
111
// Find specific exception type in cause chain
112
public <T extends Throwable> T findCause(Throwable throwable, Class<T> causeType) {
113
List<Throwable> chain = ExceptionUtils.getThrowableList(throwable);
114
115
for (Throwable t : chain) {
116
if (causeType.isInstance(t)) {
117
return causeType.cast(t);
118
}
119
}
120
121
return null;
122
}
123
124
// Check if specific exception type exists in cause chain
125
public boolean hasCause(Throwable throwable, Class<? extends Throwable> causeType) {
126
return findCause(throwable, causeType) != null;
127
}
128
129
public void handleDatabaseError(Exception e) {
130
// Look for SQL exceptions in the cause chain
131
SQLException sqlException = findCause(e, SQLException.class);
132
if (sqlException != null) {
133
log.error("Database error - SQL State: {}, Error Code: {}",
134
sqlException.getSQLState(), sqlException.getErrorCode());
135
}
136
137
// Look for connection exceptions
138
if (hasCause(e, ConnectException.class)) {
139
log.error("Network connectivity issue detected in exception chain");
140
}
141
}
142
}
143
```
144
145
#### Exception Message Operations
146
147
```java { .api }
148
// Message extraction
149
public static String getMessage(Throwable th)
150
public static String getStackTrace(Throwable throwable)
151
152
// Throwable iteration
153
public static void forEach(Throwable throwable, Consumer<Throwable> consumer)
154
public static <T extends Throwable> T asRuntimeException(Throwable throwable)
155
```
156
157
**Usage Examples:**
158
```java { .api }
159
public class MessageExamples {
160
161
public String extractBestMessage(Throwable throwable) {
162
// Get the most informative message from exception chain
163
String message = ExceptionUtils.getMessage(throwable);
164
if (StringUtils.isBlank(message)) {
165
// Fall back to root cause message if main message is empty
166
message = ExceptionUtils.getRootCauseMessage(throwable);
167
}
168
169
return StringUtils.defaultString(message, "Unknown error");
170
}
171
172
public void analyzeAllExceptionsInChain(Exception exception) {
173
// Process each exception in the chain
174
ExceptionUtils.forEach(exception, throwable -> {
175
log.debug("Processing exception: {} - {}",
176
throwable.getClass().getSimpleName(),
177
throwable.getMessage());
178
179
// Check for specific patterns
180
if (throwable instanceof TimeoutException) {
181
log.warn("Timeout detected in exception chain");
182
}
183
184
if (throwable instanceof SecurityException) {
185
log.error("Security violation detected: {}", throwable.getMessage());
186
}
187
});
188
}
189
190
// Convert checked exceptions to runtime exceptions
191
public void performOperationUnchecked() {
192
try {
193
performRiskyOperation();
194
} catch (CheckedException e) {
195
// Convert to runtime exception while preserving stack trace
196
throw ExceptionUtils.asRuntimeException(e);
197
}
198
}
199
}
200
```
201
202
### Contextual Exception Classes
203
204
#### ContextedException - Exception with Context Information
205
206
Allows adding contextual information to exceptions without losing the original stack trace:
207
208
```java { .api }
209
import org.apache.commons.lang3.exception.ContextedException;
210
```
211
212
```java { .api }
213
public class ContextedExceptionExample {
214
215
public void processUserData(String userId, Map<String, Object> data) {
216
try {
217
validateUserData(data);
218
updateUserProfile(userId, data);
219
} catch (ValidationException e) {
220
// Add context to the exception
221
ContextedException contextedException = new ContextedException("User data processing failed", e);
222
contextedException.addContextValue("userId", userId);
223
contextedException.addContextValue("dataKeys", data.keySet());
224
contextedException.addContextValue("timestamp", new Date());
225
contextedException.addContextValue("processingNode", getServerNodeId());
226
227
throw contextedException;
228
} catch (DatabaseException e) {
229
ContextedException contextedException = new ContextedException("Database operation failed", e);
230
contextedException.addContextValue("operation", "updateUserProfile");
231
contextedException.addContextValue("userId", userId);
232
contextedException.addContextValue("affectedFields", data.keySet());
233
234
throw contextedException;
235
}
236
}
237
238
public void handleContextedException(ContextedException e) {
239
log.error("Contexted exception occurred: {}", e.getMessage());
240
241
// Access context information
242
for (Pair<String, Object> context : e.getContextEntries()) {
243
log.error(" Context - {}: {}", context.getKey(), context.getValue());
244
}
245
246
// Get specific context values
247
String userId = (String) e.getFirstContextValue("userId");
248
Date timestamp = (Date) e.getFirstContextValue("timestamp");
249
250
if (userId != null) {
251
log.error("Failed operation involved user: {}", userId);
252
}
253
254
// Get formatted context
255
String contextString = e.getFormattedExceptionMessage();
256
log.error("Full context:\n{}", contextString);
257
}
258
}
259
```
260
261
#### ContextedRuntimeException - Runtime Exception with Context
262
263
Similar to ContextedException but extends RuntimeException:
264
265
```java { .api }
266
import org.apache.commons.lang3.exception.ContextedRuntimeException;
267
```
268
269
```java { .api }
270
public class ContextedRuntimeExceptionExample {
271
272
public void processPayment(PaymentRequest request) {
273
try {
274
validatePaymentRequest(request);
275
chargePaymentMethod(request);
276
recordTransaction(request);
277
} catch (PaymentValidationException e) {
278
ContextedRuntimeException runtimeException =
279
new ContextedRuntimeException("Payment processing validation failed", e);
280
281
runtimeException.addContextValue("requestId", request.getId());
282
runtimeException.addContextValue("amount", request.getAmount());
283
runtimeException.addContextValue("currency", request.getCurrency());
284
runtimeException.addContextValue("paymentMethod", request.getPaymentMethod().getType());
285
runtimeException.addContextValue("merchantId", request.getMerchantId());
286
287
throw runtimeException;
288
289
} catch (PaymentGatewayException e) {
290
ContextedRuntimeException runtimeException =
291
new ContextedRuntimeException("Payment gateway communication failed", e);
292
293
runtimeException.addContextValue("gateway", e.getGatewayName());
294
runtimeException.addContextValue("gatewayResponseCode", e.getResponseCode());
295
runtimeException.addContextValue("requestId", request.getId());
296
runtimeException.addContextValue("retryCount", getRetryCount(request.getId()));
297
298
throw runtimeException;
299
}
300
}
301
}
302
```
303
304
### Custom Exception Utilities
305
306
#### Exception Builder Pattern
307
308
```java { .api }
309
public class ExceptionBuilder {
310
311
public static class Builder {
312
private String message;
313
private Throwable cause;
314
private final Map<String, Object> context = new LinkedHashMap<>();
315
316
public Builder message(String message) {
317
this.message = message;
318
return this;
319
}
320
321
public Builder cause(Throwable cause) {
322
this.cause = cause;
323
return this;
324
}
325
326
public Builder context(String key, Object value) {
327
this.context.put(key, value);
328
return this;
329
}
330
331
public Builder userContext(String userId, String operation) {
332
return context("userId", userId)
333
.context("operation", operation)
334
.context("timestamp", new Date());
335
}
336
337
public Builder requestContext(String requestId, String endpoint) {
338
return context("requestId", requestId)
339
.context("endpoint", endpoint)
340
.context("serverNode", getServerNodeId());
341
}
342
343
public ContextedRuntimeException buildRuntime() {
344
ContextedRuntimeException exception = new ContextedRuntimeException(message, cause);
345
context.forEach(exception::addContextValue);
346
return exception;
347
}
348
349
public ContextedException buildChecked() {
350
ContextedException exception = new ContextedException(message, cause);
351
context.forEach(exception::addContextValue);
352
return exception;
353
}
354
}
355
356
public static Builder builder() {
357
return new Builder();
358
}
359
}
360
361
// Usage examples
362
public class ExceptionBuilderUsage {
363
364
public void processOrder(String orderId, String userId) {
365
try {
366
Order order = loadOrder(orderId);
367
validateOrder(order, userId);
368
} catch (OrderNotFoundException e) {
369
throw ExceptionBuilder.builder()
370
.message("Order processing failed - order not found")
371
.cause(e)
372
.userContext(userId, "processOrder")
373
.context("orderId", orderId)
374
.buildRuntime();
375
} catch (ValidationException e) {
376
throw ExceptionBuilder.builder()
377
.message("Order validation failed")
378
.cause(e)
379
.userContext(userId, "validateOrder")
380
.context("orderId", orderId)
381
.context("validationErrors", e.getErrors())
382
.buildRuntime();
383
}
384
}
385
386
public void handleApiRequest(String requestId, String endpoint) {
387
try {
388
processApiRequest(endpoint);
389
} catch (Exception e) {
390
throw ExceptionBuilder.builder()
391
.message("API request processing failed")
392
.cause(e)
393
.requestContext(requestId, endpoint)
394
.context("userAgent", getCurrentUserAgent())
395
.context("clientIp", getCurrentClientIp())
396
.buildRuntime();
397
}
398
}
399
}
400
```
401
402
## Advanced Exception Handling Patterns
403
404
### Exception Chain Analysis Utilities
405
406
```java { .api }
407
public final class ExceptionAnalyzer {
408
409
// Find all exceptions of specific types in the chain
410
public static <T extends Throwable> List<T> findCauses(Throwable throwable, Class<T> causeType) {
411
List<T> causes = new ArrayList<>();
412
ExceptionUtils.forEach(throwable, t -> {
413
if (causeType.isInstance(t)) {
414
causes.add(causeType.cast(t));
415
}
416
});
417
return causes;
418
}
419
420
// Get exception chain summary
421
public static ExceptionSummary summarize(Throwable throwable) {
422
List<Throwable> chain = ExceptionUtils.getThrowableList(throwable);
423
424
return ExceptionSummary.builder()
425
.totalExceptions(chain.size())
426
.rootCause(ExceptionUtils.getRootCause(throwable))
427
.rootCauseMessage(ExceptionUtils.getRootCauseMessage(throwable))
428
.exceptionTypes(chain.stream()
429
.map(t -> t.getClass().getSimpleName())
430
.collect(Collectors.toList()))
431
.hasTimeout(chain.stream().anyMatch(t -> t instanceof TimeoutException))
432
.hasNetworkError(chain.stream().anyMatch(t -> isNetworkException(t)))
433
.hasSecurityError(chain.stream().anyMatch(t -> t instanceof SecurityException))
434
.build();
435
}
436
437
// Extract structured error information
438
public static ErrorInfo extractErrorInfo(Throwable throwable) {
439
ErrorInfo.Builder builder = ErrorInfo.builder()
440
.message(ExceptionUtils.getMessage(throwable))
441
.type(throwable.getClass().getSimpleName())
442
.timestamp(new Date());
443
444
// Add context if available
445
if (throwable instanceof ContextedException) {
446
ContextedException ce = (ContextedException) throwable;
447
Map<String, Object> context = new HashMap<>();
448
ce.getContextEntries().forEach(pair -> context.put(pair.getKey(), pair.getValue()));
449
builder.context(context);
450
}
451
452
// Add root cause information
453
Throwable rootCause = ExceptionUtils.getRootCause(throwable);
454
if (rootCause != null && rootCause != throwable) {
455
builder.rootCauseType(rootCause.getClass().getSimpleName())
456
.rootCauseMessage(rootCause.getMessage());
457
}
458
459
return builder.build();
460
}
461
462
// Check for specific error categories
463
public static boolean isRetryableError(Throwable throwable) {
464
return ExceptionUtils.getThrowableList(throwable).stream()
465
.anyMatch(t ->
466
t instanceof ConnectException ||
467
t instanceof SocketTimeoutException ||
468
t instanceof InterruptedException ||
469
(t instanceof SQLException && isTransientSqlError((SQLException) t))
470
);
471
}
472
473
public static boolean isUserError(Throwable throwable) {
474
return ExceptionUtils.getThrowableList(throwable).stream()
475
.anyMatch(t ->
476
t instanceof IllegalArgumentException ||
477
t instanceof ValidationException ||
478
t instanceof AuthenticationException ||
479
t instanceof AuthorizationException
480
);
481
}
482
483
private static boolean isNetworkException(Throwable throwable) {
484
return throwable instanceof ConnectException ||
485
throwable instanceof UnknownHostException ||
486
throwable instanceof SocketException ||
487
throwable instanceof SocketTimeoutException;
488
}
489
490
private static boolean isTransientSqlError(SQLException e) {
491
// Check for common transient SQL error codes
492
String sqlState = e.getSQLState();
493
return sqlState != null && (
494
sqlState.startsWith("08") || // Connection errors
495
sqlState.startsWith("40") || // Transaction rollback
496
sqlState.equals("HY000") // General error (often transient)
497
);
498
}
499
}
500
```
501
502
### Exception Reporting and Monitoring
503
504
```java { .api }
505
@Component
506
public class ExceptionReportingService {
507
508
private final MeterRegistry meterRegistry;
509
private final NotificationService notificationService;
510
511
// Exception metrics
512
public void reportException(Throwable throwable, String context) {
513
ExceptionSummary summary = ExceptionAnalyzer.summarize(throwable);
514
515
// Record metrics
516
Counter.builder("exceptions.total")
517
.tag("type", throwable.getClass().getSimpleName())
518
.tag("context", context)
519
.tag("has_timeout", String.valueOf(summary.hasTimeout()))
520
.tag("has_network_error", String.valueOf(summary.hasNetworkError()))
521
.register(meterRegistry)
522
.increment();
523
524
// Alert on critical errors
525
if (isCriticalError(throwable)) {
526
AlertInfo alert = createAlert(throwable, context, summary);
527
notificationService.sendAlert(alert);
528
}
529
530
// Log structured error information
531
ErrorInfo errorInfo = ExceptionAnalyzer.extractErrorInfo(throwable);
532
log.error("Exception reported: {}", errorInfo.toJson());
533
}
534
535
private boolean isCriticalError(Throwable throwable) {
536
return ExceptionUtils.getThrowableList(throwable).stream()
537
.anyMatch(t ->
538
t instanceof OutOfMemoryError ||
539
t instanceof StackOverflowError ||
540
t instanceof SecurityException ||
541
(t instanceof SQLException && !ExceptionAnalyzer.isRetryableError(t))
542
);
543
}
544
545
// Exception aggregation for dashboards
546
@Scheduled(fixedRate = 60000) // Every minute
547
public void aggregateExceptionMetrics() {
548
ExceptionMetrics metrics = gatherExceptionMetrics();
549
550
Gauge.builder("exceptions.error_rate")
551
.register(meterRegistry, metrics, m -> m.getErrorRate());
552
553
Gauge.builder("exceptions.unique_types")
554
.register(meterRegistry, metrics, m -> m.getUniqueExceptionTypes());
555
}
556
}
557
```
558
559
### Integration Examples
560
561
#### Spring Boot Global Exception Handler
562
563
```java { .api }
564
@ControllerAdvice
565
public class GlobalExceptionHandler {
566
567
private final ExceptionReportingService reportingService;
568
569
@ExceptionHandler(ContextedException.class)
570
public ResponseEntity<ErrorResponse> handleContextedException(ContextedException e) {
571
reportingService.reportException(e, "web-request");
572
573
ErrorResponse response = ErrorResponse.builder()
574
.message("An error occurred while processing your request")
575
.code("PROCESSING_ERROR")
576
.timestamp(new Date())
577
.details(extractPublicContext(e))
578
.build();
579
580
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
581
}
582
583
@ExceptionHandler(ValidationException.class)
584
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException e) {
585
reportingService.reportException(e, "validation");
586
587
ErrorResponse response = ErrorResponse.builder()
588
.message("Validation failed")
589
.code("VALIDATION_ERROR")
590
.timestamp(new Date())
591
.details(Map.of("errors", e.getValidationErrors()))
592
.build();
593
594
return ResponseEntity.badRequest().body(response);
595
}
596
597
@ExceptionHandler(Exception.class)
598
public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
599
// Wrap in contexted exception with request information
600
ContextedRuntimeException contextedException = ExceptionBuilder.builder()
601
.message("Unexpected error occurred")
602
.cause(e)
603
.requestContext(getCurrentRequestId(), getCurrentEndpoint())
604
.context("userAgent", getCurrentUserAgent())
605
.buildRuntime();
606
607
reportingService.reportException(contextedException, "unexpected");
608
609
ErrorResponse response = ErrorResponse.builder()
610
.message("An unexpected error occurred")
611
.code("INTERNAL_ERROR")
612
.timestamp(new Date())
613
.build();
614
615
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
616
}
617
618
private Map<String, Object> extractPublicContext(ContextedException e) {
619
Map<String, Object> publicContext = new HashMap<>();
620
621
// Only include non-sensitive context information
622
for (Pair<String, Object> entry : e.getContextEntries()) {
623
String key = entry.getKey();
624
if (!isSensitiveContextKey(key)) {
625
publicContext.put(key, entry.getValue());
626
}
627
}
628
629
return publicContext;
630
}
631
632
private boolean isSensitiveContextKey(String key) {
633
return key.toLowerCase().contains("password") ||
634
key.toLowerCase().contains("token") ||
635
key.toLowerCase().contains("secret") ||
636
key.toLowerCase().contains("key");
637
}
638
}
639
```
640
641
#### Async Exception Handling
642
643
```java { .api }
644
@Service
645
public class AsyncExceptionHandler {
646
647
@EventListener
648
public void handleAsyncException(AsyncExceptionEvent event) {
649
Throwable exception = event.getException();
650
String taskName = event.getTaskName();
651
652
// Add async context
653
ContextedRuntimeException contextedException = ExceptionBuilder.builder()
654
.message("Async task failed")
655
.cause(exception)
656
.context("taskName", taskName)
657
.context("threadName", Thread.currentThread().getName())
658
.context("executionTime", event.getExecutionTime())
659
.buildRuntime();
660
661
// Report for monitoring
662
exceptionReportingService.reportException(contextedException, "async-task");
663
664
// Decide on retry strategy
665
if (ExceptionAnalyzer.isRetryableError(exception)) {
666
scheduleRetry(taskName, event.getTaskData());
667
} else {
668
handleFailedTask(taskName, contextedException);
669
}
670
}
671
672
@Async
673
public CompletableFuture<Void> handleExceptionAsync(Exception e, String context) {
674
return CompletableFuture.runAsync(() -> {
675
try {
676
processException(e, context);
677
} catch (Exception processingError) {
678
// Avoid infinite loops in exception handling
679
log.error("Error while processing exception: {}", processingError.getMessage());
680
}
681
});
682
}
683
}
684
```
685
686
The exception utilities in Apache Commons Lang provide comprehensive tools for robust error handling, context preservation, and exception analysis that are essential for building maintainable and debuggable Java applications.