Why This Happens
When a Callable or Runnable submitted to an ExecutorService throws an exception, it is wrapped in an ExecutionException and re-thrown when you call Future.get(). The actual exception is available via getCause(). Always unwrap to find the real error.
The Problem
Future<String> future = executor.submit(() -> {
throw new RuntimeException("DB connection failed");
});
try {
future.get();
} catch (ExecutionException e) {
System.out.println(e.getMessage()); // Unhelpful wrapped message
}The Fix
Future<String> future = executor.submit(() -> {
throw new RuntimeException("DB connection failed");
});
try {
future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // Unwrap the real exception
System.err.println("Task failed: " + cause.getMessage());
cause.printStackTrace();
}Step-by-Step Fix
- 1
Identify the ExecutionException
Find the Future.get() call that throws ExecutionException.
- 2
Unwrap the cause
Call getCause() to get the actual exception that the task threw.
- 3
Fix the underlying error
Debug and fix the root cause exception, not the ExecutionException wrapper.
Bugsly catches this automatically
Bugsly's AI analyzes this error pattern in real-time, explains what went wrong in plain English, and suggests the exact fix — before your users even report it.
Try Bugsly free