Apache Commons Lang provides essential Java utility classes for string manipulation, object operations, array handling, date/time processing, reflection utilities, and more.
—
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.
Provides 35 static methods for analyzing exception hierarchies, extracting stack traces, and handling exception chains:
import org.apache.commons.lang3.exception.ExceptionUtils;// Stack trace extraction
public static String getStackTrace(Throwable throwable)
public static String[] getStackFrames(Throwable throwable)
public static List<String> getStackFrameList(Throwable throwable)
// Root cause stack traces
public static String[] getRootCauseStackTrace(Throwable throwable)
public static List<String> getRootCauseStackTraceList(Throwable throwable)Usage Examples:
public class StackTraceExamples {
public void demonstrateStackTraceOperations() {
try {
riskyOperation();
} catch (Exception e) {
// Get complete stack trace as string
String fullStackTrace = ExceptionUtils.getStackTrace(e);
log.error("Full stack trace:\n{}", fullStackTrace);
// Get stack trace as array of frames
String[] frames = ExceptionUtils.getStackFrames(e);
for (int i = 0; i < Math.min(5, frames.length); i++) {
log.debug("Frame {}: {}", i, frames[i]);
}
// Get root cause stack trace (if exception has causes)
String[] rootFrames = ExceptionUtils.getRootCauseStackTrace(e);
log.error("Root cause stack trace has {} frames", rootFrames.length);
}
}
// Custom logging with stack trace details
public void logExceptionDetails(Exception e) {
StringBuilder details = new StringBuilder();
details.append("Exception Details:\n");
details.append("Type: ").append(e.getClass().getSimpleName()).append("\n");
details.append("Message: ").append(e.getMessage()).append("\n");
// Add stack trace information
List<String> stackFrames = ExceptionUtils.getStackFrameList(e);
details.append("Stack frames (top 5):\n");
for (int i = 0; i < Math.min(5, stackFrames.size()); i++) {
details.append(" ").append(i + 1).append(". ").append(stackFrames.get(i)).append("\n");
}
log.error(details.toString());
}
}// Root cause extraction
public static Throwable getRootCause(Throwable throwable)
public static String getRootCauseMessage(Throwable throwable)
// Cause chain navigation
public static Throwable getCause(Throwable throwable)
public static Throwable getCause(Throwable throwable, String[] methodNames)
public static int getThrowableCount(Throwable throwable)
public static Throwable[] getThrowables(Throwable throwable)
public static List<Throwable> getThrowableList(Throwable throwable)Usage Examples:
public class CauseChainExamples {
public void analyzeCauseChain(Exception exception) {
// Get root cause
Throwable rootCause = ExceptionUtils.getRootCause(exception);
if (rootCause != null) {
log.error("Root cause: {} - {}", rootCause.getClass().getSimpleName(), rootCause.getMessage());
}
// Get root cause message (handles null safely)
String rootMessage = ExceptionUtils.getRootCauseMessage(exception);
log.info("Root cause message: {}", rootMessage);
// Analyze complete cause chain
List<Throwable> throwableChain = ExceptionUtils.getThrowableList(exception);
log.info("Exception chain has {} levels:", throwableChain.size());
for (int i = 0; i < throwableChain.size(); i++) {
Throwable t = throwableChain.get(i);
log.info(" Level {}: {} - {}", i, t.getClass().getSimpleName(), t.getMessage());
}
}
// Find specific exception type in cause chain
public <T extends Throwable> T findCause(Throwable throwable, Class<T> causeType) {
List<Throwable> chain = ExceptionUtils.getThrowableList(throwable);
for (Throwable t : chain) {
if (causeType.isInstance(t)) {
return causeType.cast(t);
}
}
return null;
}
// Check if specific exception type exists in cause chain
public boolean hasCause(Throwable throwable, Class<? extends Throwable> causeType) {
return findCause(throwable, causeType) != null;
}
public void handleDatabaseError(Exception e) {
// Look for SQL exceptions in the cause chain
SQLException sqlException = findCause(e, SQLException.class);
if (sqlException != null) {
log.error("Database error - SQL State: {}, Error Code: {}",
sqlException.getSQLState(), sqlException.getErrorCode());
}
// Look for connection exceptions
if (hasCause(e, ConnectException.class)) {
log.error("Network connectivity issue detected in exception chain");
}
}
}// Message extraction
public static String getMessage(Throwable th)
public static String getStackTrace(Throwable throwable)
// Throwable iteration
public static void forEach(Throwable throwable, Consumer<Throwable> consumer)
public static <T extends Throwable> T asRuntimeException(Throwable throwable)Usage Examples:
public class MessageExamples {
public String extractBestMessage(Throwable throwable) {
// Get the most informative message from exception chain
String message = ExceptionUtils.getMessage(throwable);
if (StringUtils.isBlank(message)) {
// Fall back to root cause message if main message is empty
message = ExceptionUtils.getRootCauseMessage(throwable);
}
return StringUtils.defaultString(message, "Unknown error");
}
public void analyzeAllExceptionsInChain(Exception exception) {
// Process each exception in the chain
ExceptionUtils.forEach(exception, throwable -> {
log.debug("Processing exception: {} - {}",
throwable.getClass().getSimpleName(),
throwable.getMessage());
// Check for specific patterns
if (throwable instanceof TimeoutException) {
log.warn("Timeout detected in exception chain");
}
if (throwable instanceof SecurityException) {
log.error("Security violation detected: {}", throwable.getMessage());
}
});
}
// Convert checked exceptions to runtime exceptions
public void performOperationUnchecked() {
try {
performRiskyOperation();
} catch (CheckedException e) {
// Convert to runtime exception while preserving stack trace
throw ExceptionUtils.asRuntimeException(e);
}
}
}Allows adding contextual information to exceptions without losing the original stack trace:
import org.apache.commons.lang3.exception.ContextedException;public class ContextedExceptionExample {
public void processUserData(String userId, Map<String, Object> data) {
try {
validateUserData(data);
updateUserProfile(userId, data);
} catch (ValidationException e) {
// Add context to the exception
ContextedException contextedException = new ContextedException("User data processing failed", e);
contextedException.addContextValue("userId", userId);
contextedException.addContextValue("dataKeys", data.keySet());
contextedException.addContextValue("timestamp", new Date());
contextedException.addContextValue("processingNode", getServerNodeId());
throw contextedException;
} catch (DatabaseException e) {
ContextedException contextedException = new ContextedException("Database operation failed", e);
contextedException.addContextValue("operation", "updateUserProfile");
contextedException.addContextValue("userId", userId);
contextedException.addContextValue("affectedFields", data.keySet());
throw contextedException;
}
}
public void handleContextedException(ContextedException e) {
log.error("Contexted exception occurred: {}", e.getMessage());
// Access context information
for (Pair<String, Object> context : e.getContextEntries()) {
log.error(" Context - {}: {}", context.getKey(), context.getValue());
}
// Get specific context values
String userId = (String) e.getFirstContextValue("userId");
Date timestamp = (Date) e.getFirstContextValue("timestamp");
if (userId != null) {
log.error("Failed operation involved user: {}", userId);
}
// Get formatted context
String contextString = e.getFormattedExceptionMessage();
log.error("Full context:\n{}", contextString);
}
}Similar to ContextedException but extends RuntimeException:
import org.apache.commons.lang3.exception.ContextedRuntimeException;public class ContextedRuntimeExceptionExample {
public void processPayment(PaymentRequest request) {
try {
validatePaymentRequest(request);
chargePaymentMethod(request);
recordTransaction(request);
} catch (PaymentValidationException e) {
ContextedRuntimeException runtimeException =
new ContextedRuntimeException("Payment processing validation failed", e);
runtimeException.addContextValue("requestId", request.getId());
runtimeException.addContextValue("amount", request.getAmount());
runtimeException.addContextValue("currency", request.getCurrency());
runtimeException.addContextValue("paymentMethod", request.getPaymentMethod().getType());
runtimeException.addContextValue("merchantId", request.getMerchantId());
throw runtimeException;
} catch (PaymentGatewayException e) {
ContextedRuntimeException runtimeException =
new ContextedRuntimeException("Payment gateway communication failed", e);
runtimeException.addContextValue("gateway", e.getGatewayName());
runtimeException.addContextValue("gatewayResponseCode", e.getResponseCode());
runtimeException.addContextValue("requestId", request.getId());
runtimeException.addContextValue("retryCount", getRetryCount(request.getId()));
throw runtimeException;
}
}
}public class ExceptionBuilder {
public static class Builder {
private String message;
private Throwable cause;
private final Map<String, Object> context = new LinkedHashMap<>();
public Builder message(String message) {
this.message = message;
return this;
}
public Builder cause(Throwable cause) {
this.cause = cause;
return this;
}
public Builder context(String key, Object value) {
this.context.put(key, value);
return this;
}
public Builder userContext(String userId, String operation) {
return context("userId", userId)
.context("operation", operation)
.context("timestamp", new Date());
}
public Builder requestContext(String requestId, String endpoint) {
return context("requestId", requestId)
.context("endpoint", endpoint)
.context("serverNode", getServerNodeId());
}
public ContextedRuntimeException buildRuntime() {
ContextedRuntimeException exception = new ContextedRuntimeException(message, cause);
context.forEach(exception::addContextValue);
return exception;
}
public ContextedException buildChecked() {
ContextedException exception = new ContextedException(message, cause);
context.forEach(exception::addContextValue);
return exception;
}
}
public static Builder builder() {
return new Builder();
}
}
// Usage examples
public class ExceptionBuilderUsage {
public void processOrder(String orderId, String userId) {
try {
Order order = loadOrder(orderId);
validateOrder(order, userId);
} catch (OrderNotFoundException e) {
throw ExceptionBuilder.builder()
.message("Order processing failed - order not found")
.cause(e)
.userContext(userId, "processOrder")
.context("orderId", orderId)
.buildRuntime();
} catch (ValidationException e) {
throw ExceptionBuilder.builder()
.message("Order validation failed")
.cause(e)
.userContext(userId, "validateOrder")
.context("orderId", orderId)
.context("validationErrors", e.getErrors())
.buildRuntime();
}
}
public void handleApiRequest(String requestId, String endpoint) {
try {
processApiRequest(endpoint);
} catch (Exception e) {
throw ExceptionBuilder.builder()
.message("API request processing failed")
.cause(e)
.requestContext(requestId, endpoint)
.context("userAgent", getCurrentUserAgent())
.context("clientIp", getCurrentClientIp())
.buildRuntime();
}
}
}public final class ExceptionAnalyzer {
// Find all exceptions of specific types in the chain
public static <T extends Throwable> List<T> findCauses(Throwable throwable, Class<T> causeType) {
List<T> causes = new ArrayList<>();
ExceptionUtils.forEach(throwable, t -> {
if (causeType.isInstance(t)) {
causes.add(causeType.cast(t));
}
});
return causes;
}
// Get exception chain summary
public static ExceptionSummary summarize(Throwable throwable) {
List<Throwable> chain = ExceptionUtils.getThrowableList(throwable);
return ExceptionSummary.builder()
.totalExceptions(chain.size())
.rootCause(ExceptionUtils.getRootCause(throwable))
.rootCauseMessage(ExceptionUtils.getRootCauseMessage(throwable))
.exceptionTypes(chain.stream()
.map(t -> t.getClass().getSimpleName())
.collect(Collectors.toList()))
.hasTimeout(chain.stream().anyMatch(t -> t instanceof TimeoutException))
.hasNetworkError(chain.stream().anyMatch(t -> isNetworkException(t)))
.hasSecurityError(chain.stream().anyMatch(t -> t instanceof SecurityException))
.build();
}
// Extract structured error information
public static ErrorInfo extractErrorInfo(Throwable throwable) {
ErrorInfo.Builder builder = ErrorInfo.builder()
.message(ExceptionUtils.getMessage(throwable))
.type(throwable.getClass().getSimpleName())
.timestamp(new Date());
// Add context if available
if (throwable instanceof ContextedException) {
ContextedException ce = (ContextedException) throwable;
Map<String, Object> context = new HashMap<>();
ce.getContextEntries().forEach(pair -> context.put(pair.getKey(), pair.getValue()));
builder.context(context);
}
// Add root cause information
Throwable rootCause = ExceptionUtils.getRootCause(throwable);
if (rootCause != null && rootCause != throwable) {
builder.rootCauseType(rootCause.getClass().getSimpleName())
.rootCauseMessage(rootCause.getMessage());
}
return builder.build();
}
// Check for specific error categories
public static boolean isRetryableError(Throwable throwable) {
return ExceptionUtils.getThrowableList(throwable).stream()
.anyMatch(t ->
t instanceof ConnectException ||
t instanceof SocketTimeoutException ||
t instanceof InterruptedException ||
(t instanceof SQLException && isTransientSqlError((SQLException) t))
);
}
public static boolean isUserError(Throwable throwable) {
return ExceptionUtils.getThrowableList(throwable).stream()
.anyMatch(t ->
t instanceof IllegalArgumentException ||
t instanceof ValidationException ||
t instanceof AuthenticationException ||
t instanceof AuthorizationException
);
}
private static boolean isNetworkException(Throwable throwable) {
return throwable instanceof ConnectException ||
throwable instanceof UnknownHostException ||
throwable instanceof SocketException ||
throwable instanceof SocketTimeoutException;
}
private static boolean isTransientSqlError(SQLException e) {
// Check for common transient SQL error codes
String sqlState = e.getSQLState();
return sqlState != null && (
sqlState.startsWith("08") || // Connection errors
sqlState.startsWith("40") || // Transaction rollback
sqlState.equals("HY000") // General error (often transient)
);
}
}@Component
public class ExceptionReportingService {
private final MeterRegistry meterRegistry;
private final NotificationService notificationService;
// Exception metrics
public void reportException(Throwable throwable, String context) {
ExceptionSummary summary = ExceptionAnalyzer.summarize(throwable);
// Record metrics
Counter.builder("exceptions.total")
.tag("type", throwable.getClass().getSimpleName())
.tag("context", context)
.tag("has_timeout", String.valueOf(summary.hasTimeout()))
.tag("has_network_error", String.valueOf(summary.hasNetworkError()))
.register(meterRegistry)
.increment();
// Alert on critical errors
if (isCriticalError(throwable)) {
AlertInfo alert = createAlert(throwable, context, summary);
notificationService.sendAlert(alert);
}
// Log structured error information
ErrorInfo errorInfo = ExceptionAnalyzer.extractErrorInfo(throwable);
log.error("Exception reported: {}", errorInfo.toJson());
}
private boolean isCriticalError(Throwable throwable) {
return ExceptionUtils.getThrowableList(throwable).stream()
.anyMatch(t ->
t instanceof OutOfMemoryError ||
t instanceof StackOverflowError ||
t instanceof SecurityException ||
(t instanceof SQLException && !ExceptionAnalyzer.isRetryableError(t))
);
}
// Exception aggregation for dashboards
@Scheduled(fixedRate = 60000) // Every minute
public void aggregateExceptionMetrics() {
ExceptionMetrics metrics = gatherExceptionMetrics();
Gauge.builder("exceptions.error_rate")
.register(meterRegistry, metrics, m -> m.getErrorRate());
Gauge.builder("exceptions.unique_types")
.register(meterRegistry, metrics, m -> m.getUniqueExceptionTypes());
}
}@ControllerAdvice
public class GlobalExceptionHandler {
private final ExceptionReportingService reportingService;
@ExceptionHandler(ContextedException.class)
public ResponseEntity<ErrorResponse> handleContextedException(ContextedException e) {
reportingService.reportException(e, "web-request");
ErrorResponse response = ErrorResponse.builder()
.message("An error occurred while processing your request")
.code("PROCESSING_ERROR")
.timestamp(new Date())
.details(extractPublicContext(e))
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException e) {
reportingService.reportException(e, "validation");
ErrorResponse response = ErrorResponse.builder()
.message("Validation failed")
.code("VALIDATION_ERROR")
.timestamp(new Date())
.details(Map.of("errors", e.getValidationErrors()))
.build();
return ResponseEntity.badRequest().body(response);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
// Wrap in contexted exception with request information
ContextedRuntimeException contextedException = ExceptionBuilder.builder()
.message("Unexpected error occurred")
.cause(e)
.requestContext(getCurrentRequestId(), getCurrentEndpoint())
.context("userAgent", getCurrentUserAgent())
.buildRuntime();
reportingService.reportException(contextedException, "unexpected");
ErrorResponse response = ErrorResponse.builder()
.message("An unexpected error occurred")
.code("INTERNAL_ERROR")
.timestamp(new Date())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
private Map<String, Object> extractPublicContext(ContextedException e) {
Map<String, Object> publicContext = new HashMap<>();
// Only include non-sensitive context information
for (Pair<String, Object> entry : e.getContextEntries()) {
String key = entry.getKey();
if (!isSensitiveContextKey(key)) {
publicContext.put(key, entry.getValue());
}
}
return publicContext;
}
private boolean isSensitiveContextKey(String key) {
return key.toLowerCase().contains("password") ||
key.toLowerCase().contains("token") ||
key.toLowerCase().contains("secret") ||
key.toLowerCase().contains("key");
}
}@Service
public class AsyncExceptionHandler {
@EventListener
public void handleAsyncException(AsyncExceptionEvent event) {
Throwable exception = event.getException();
String taskName = event.getTaskName();
// Add async context
ContextedRuntimeException contextedException = ExceptionBuilder.builder()
.message("Async task failed")
.cause(exception)
.context("taskName", taskName)
.context("threadName", Thread.currentThread().getName())
.context("executionTime", event.getExecutionTime())
.buildRuntime();
// Report for monitoring
exceptionReportingService.reportException(contextedException, "async-task");
// Decide on retry strategy
if (ExceptionAnalyzer.isRetryableError(exception)) {
scheduleRetry(taskName, event.getTaskData());
} else {
handleFailedTask(taskName, contextedException);
}
}
@Async
public CompletableFuture<Void> handleExceptionAsync(Exception e, String context) {
return CompletableFuture.runAsync(() -> {
try {
processException(e, context);
} catch (Exception processingError) {
// Avoid infinite loops in exception handling
log.error("Error while processing exception: {}", processingError.getMessage());
}
});
}
}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.
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-commons--commons-lang3