Log uncaught exceptions in Java
Register a default or per-thread exception handler, and distinguish thread termination from exceptions stored in executor futures.
Implement Thread.UncaughtExceptionHandler to log a failure that escapes a thread's run() method. Register a default handler at startup, and account separately for frameworks and executor APIs that capture exceptions themselves.
Register a default handler
import java.util.logging.Level;
import java.util.logging.Logger;
public class GlobalExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final Logger LOGGER =
Logger.getLogger(GlobalExceptionHandler.class.getName());
@Override
public void uncaughtException(Thread thread, Throwable error) {
LOGGER.log(Level.SEVERE,
"Uncaught exception in thread " + thread.getName(), error);
}
}
Register it in the application entry point:
public class Main {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new GlobalExceptionHandler());
// Start the application's work here.
}
}
The default handler is a fallback. A per-thread handler, or a thread group's handling behavior, can take precedence. Exceptions caught internally by a framework do not become uncaught thread exceptions merely because application code omitted a catch.
Set a handler for one thread
Thread thread = new Thread(() -> {
throw new RuntimeException("Example failure");
});
thread.setUncaughtExceptionHandler((t, error) -> {
System.err.println("Thread failed: " + t.getName());
error.printStackTrace(System.err);
});
thread.start();
Printing the throwable preserves the stack trace; printing only getMessage() loses the location and cause chain.
Handle executor failures separately
For a ThreadPoolExecutor, an exception escaping a plain Runnable submitted with execute() can terminate its worker thread and reach an uncaught exception handler. submit() wraps work in a Future, which captures the failure instead. Observe the future with get() and handle its ExecutionException, or implement a task-supervision mechanism appropriate to the executor.
Do not generalize this behavior to every executor or framework. For example, code that catches an exception inside its own runnable prevents that exception from reaching the thread handler.
Frequently asked questions
Does Spring Boot register everything I need?
@ControllerAdvice and @ExceptionHandler apply to controller exception handling. A default thread handler can still help with failures that escape other threads, but it does not cover exceptions retained in futures or handled by framework-specific mechanisms.
Does the process exit after an uncaught exception?
The affected thread terminates. JVM shutdown begins when no started non-daemon threads remain. An uncaught exception in main therefore does not necessarily stop a process whose other non-daemon threads are still running.
Can I use SLF4J or Log4j 2?
Yes. Replace the logging call with your chosen framework's throwable overload, such as logger.error("Thread failed", error). The thread and executor rules are unchanged.
See the Java Thread API.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan