Log uncaught exceptions in Java

Register a default or per-thread exception handler, and distinguish thread termination from exceptions stored in executor futures.

Best practices
Log uncaught exceptions in Java

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.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting