Daemon Thread in Java: JVM Exit Rules and Examples

Learn what a daemon thread in Java is, how setDaemon() works, why the JVM does not wait for it, virtual threads, executors and when to use one.

Timeline showing the JVM exiting when the last user thread ends while a daemon thread is stopped in the middle of its loop

A daemon thread in Java is a thread that does not keep the JVM running, so the JVM exits as soon as every non-daemon (user) thread has finished, and it stops any daemon threads that are still running at that moment. We mark a platform thread as daemon with setDaemon(true) before start(), and every virtual thread is a daemon thread.

We use daemon threads for background helpers whose work can be dropped when the app ends, such as a cache sweeper, a metrics reporter or a heartbeat sender, and never for work that must complete, such as writing an order to the database.

The following example creates one daemon thread and one user thread and checks the flag on each.

Thread sweeper = new Thread(() -> {}, "cache-sweeper");
sweeper.setDaemon(true);                          // must happen before start()
boolean daemon = sweeper.isDaemon();              // true

Thread importer = new Thread(() -> {}, "csv-import");
boolean user = importer.isDaemon();               // false, inherited from main

Thread virtual = Thread.ofVirtual().unstarted(() -> {});
boolean alwaysDaemon = virtual.isDaemon();        // true

Notice that the daemon flag says nothing about speed or priority. It only decides whether the JVM waits for the thread before it exits. We look at that exit rule, how to create daemon threads with constructors, builders and factories, what happens to their unfinished work, how executors and virtual threads fit in, and when a daemon thread is the right choice.

1. What Is a Daemon Thread?

Java has two kinds of threads. A user thread (also called a non-daemon thread) keeps the JVM alive, and the JVM waits until all of them have finished. A daemon thread does not count, so when the last user thread ends, the JVM shuts down and the daemon threads end with it, wherever they are in their code.

Timeline showing the JVM exiting when the last user thread ends while a daemon thread is stopped in the middle of its loop
The JVM waits for user threads only; a daemon thread is stopped mid-loop and its finally block never runs

The JVM terminates when all started non-daemon threads have terminated, or when System.exit() or Runtime.exit() is called. A daemon thread is still a full thread while it runs, with the same scheduling, priority range and access to objects as a user thread.

Daemon threads are not low-priority threads. Older tutorials claim they are, but the daemon flag and the priority are independent settings. The JVM’s own Reference Handler thread, for example, is a daemon thread with priority 10, the highest value.

2. Creating a Daemon Thread

The setDaemon(boolean) method marks a thread as daemon or user, and isDaemon() reads the flag. We set the flag before start(), because a call while the thread is alive throws IllegalThreadStateException.

CountDownLatch stop = new CountDownLatch(1);
Thread reporter = new Thread(() -> {
    try {
        stop.await();                             // runs until told to stop
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}, "metrics-reporter");
reporter.setDaemon(true);
reporter.start();
boolean marked = reporter.isDaemon();             // true
reporter.setDaemon(false);                        // IllegalThreadStateException, thread is alive
stop.countDown();
reporter.join();

Since Java 21, the Thread builders set the flag in the same chain as the name, and a builder can also produce a ThreadFactory for an executor.

Thread heartbeat = Thread.ofPlatform().name("heartbeat").daemon(true).start(() -> {});
boolean builderDaemon = heartbeat.isDaemon();     // true

ThreadFactory daemonFactory = Thread.ofPlatform().name("sweeper-", 1).daemon(true).factory();
boolean fromFactory = daemonFactory.newThread(() -> {}).isDaemon();   // true

2.1. The Daemon Flag Is Inherited

A new platform thread starts with the daemon flag of the thread that creates it. The main thread is a user thread, so everything it creates is a user thread unless we change it, and a daemon thread creates daemon children.

AtomicBoolean childFlag = new AtomicBoolean();
Thread parent = new Thread(() -> childFlag.set(new Thread(() -> {}).isDaemon()));
parent.setDaemon(true);
parent.start();
parent.join();
boolean inherited = childFlag.get();              // true, child of a daemon thread

2.2. The Main Thread Cannot Become a Daemon

The JVM starts the main thread before our code runs, so its flag is fixed. Calling setDaemon(true) on it throws the same IllegalThreadStateException as on any started thread.

boolean mainDaemon = Thread.currentThread().isDaemon();   // false
Thread.currentThread().setDaemon(true);                   // IllegalThreadStateException

2.3. Virtual Threads Are Always Daemon Threads

A virtual thread is always a daemon thread, and the flag cannot be turned off. Passing false throws IllegalArgumentException, whereas true is accepted and changes nothing.

Thread fetcher = Thread.ofVirtual().unstarted(() -> {});
fetcher.setDaemon(true);                          // allowed, no effect
boolean stillDaemon = fetcher.isDaemon();         // true
fetcher.setDaemon(false);                         // IllegalArgumentException: 'false' not legal for virtual threads

As a result, the JVM never waits for virtual threads. A program that starts virtual threads in main must wait for them with join(), a Future or an executor’s close(), otherwise main returns and the JVM exits before the work is done.

3. What Happens to a Daemon Thread When the JVM Exits

When the last user thread finishes, the JVM runs the registered shutdown hooks and halts. Daemon threads get no interrupt and no exception. They stop in the middle of whatever they were doing, and their finally blocks and try-with-resources cleanup do not run.

The demo flushes audit records in ten batches on a daemon thread, while main ends after about 250 milliseconds. It uses a small pause helper instead of Thread.sleep().

static void pause(long ms) {
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(ms));
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("shutdown hook runs")));

Thread flusher = new Thread(() -> {
    try {
        for (int batch = 1; batch <= 10; batch++) {
            System.out.println("flushed batch " + batch);
            pause(100);
        }
    } finally {
        System.out.println("flusher finally");    // never printed
    }
});
flusher.setDaemon(true);
flusher.start();
pause(250);
System.out.println("main ends");
flushed batch 1
flushed batch 2
flushed batch 3
main ends
shutdown hook runs

We can see that batches 4 to 10 are lost and the finally block never runs, whereas the shutdown hook does. If flusher were a user thread, the JVM would wait for all ten batches. Work that must finish therefore belongs on a user thread or an executor that we close, and cleanup that must happen at exit belongs in a shutdown hook.

4. Daemon Threads vs User Threads

The JVM exit rule is the only behavioral difference between the two kinds of threads. Everything else in the table follows from it, and several claims in older articles, such as lower priority or JVM-only creation, are not true.

AspectUser (non-daemon) threadDaemon thread
JVM exitThe JVM waits until it finishesThe JVM exits without waiting and stops it
Created byOur code, by defaultOur code with setDaemon(true), the JVM, libraries
PriorityInherited from the creator, 1 to 10Inherited from the creator, 1 to 10
finally blocks at exitRun, because the thread completesDo not run if the JVM exits first
Default formain, new Thread() in main, Executors pool threadsVirtual threads, ForkJoinPool.commonPool() workers, JVM service threads
Typical workRequests, imports, writes that must completeSweepers, monitors, heartbeats that can be dropped

5. Daemon Flags in Executors and Thread Pools

Thread pools decide the daemon flag through their ThreadFactory, and the defaults differ, which explains two common bugs. Executors.defaultThreadFactory(), used by newFixedThreadPool() and the other Executors factories, creates user threads, whereas the common ForkJoinPool behind CompletableFuture.runAsync() and parallel streams uses daemon threads.

boolean poolDaemon = Executors.defaultThreadFactory().newThread(() -> {}).isDaemon();   // false
boolean commonDaemon = CompletableFuture.supplyAsync(() -> Thread.currentThread().isDaemon()).join();   // true
  • An executor that is never shut down keeps its user threads alive, so a command-line tool or a test JVM hangs instead of exiting. Closing the executor with try-with-resources or shutdown() fixes it.
  • A CompletableFuture.runAsync() task started at the end of main runs on a daemon thread, so it can be cut off when main returns. Calling join() on the future, or passing our own executor, prevents that.

A daemon factory is a reasonable choice for a pool that only does optional background work, so a forgotten shutdown() does not block the JVM. For pools that process business data, keep user threads and close the executor properly.

6. When to Use Daemon Threads

A daemon thread fits work that runs for the whole life of the app, has no natural end, and can stop at any point without harm. The JVM itself uses daemon threads for such service tasks, for example Reference Handler, Finalizer, Signal Dispatcher and Common-Cleaner, which show up in every thread dump.

Say a product catalog service keeps prices in an in-memory cache, and entries older than five minutes must be removed. A daemon thread that sweeps the cache every 30 seconds is a good fit, because skipping the last sweep at shutdown costs nothing. A thread that sends order confirmation emails is not, because a lost email is a lost message to a customer.

ScheduledExecutorService sweeperPool = Executors.newSingleThreadScheduledExecutor(
        Thread.ofPlatform().name("price-cache-sweeper").daemon(true).factory());
sweeperPool.scheduleAtFixedRate(() -> {}, 30, 30, TimeUnit.SECONDS);
boolean sweeperIsDaemon = sweeperPool.submit(() -> Thread.currentThread().isDaemon()).get();   // true
sweeperPool.shutdown();

Other good candidates are a metrics reporter that pushes counters every minute, a heartbeat sender for a cluster, and a log appender that flushes asynchronously, provided the app also flushes on shutdown through a hook. The ScheduledExecutorService guide covers the scheduling part, and the Java concurrency hub lists the related executor tutorials.

7. Daemon Thread FAQs

Several old myths about daemon threads, such as the claim that they run at low priority, still show up in interviews and older tutorials.

7.1. Is the garbage collector a daemon thread?

Not in the sense of the Java API. HotSpot runs garbage collection on internal VM threads, such as GC Thread#0, which are not java.lang.Thread objects and have no daemon flag. Java-level helper threads like Finalizer and Reference Handler are daemon threads, and the JVM never waits for any of them before exiting.

7.2. Are daemon threads low priority?

No. A daemon thread inherits its priority from the thread that creates it, like any other thread, and we can set any value from 1 to 10. The JVM’s Reference Handler daemon thread runs at priority 10.

7.3. Does the finally block run in a daemon thread?

Only if the thread reaches it before the JVM exits. When the last user thread ends, daemon threads stop at once, and neither finally blocks nor try-with-resources cleanup run, as the output in section 3 shows.

7.4. How do we stop a daemon thread gracefully?

The same way as any thread. Call interrupt() and let the thread leave its loop, or shut down the executor that owns it. Relying on JVM exit is fine only when losing the current step does no harm. Ways to end threads are covered in how to stop a thread.

7.5. Can we call setDaemon() after start()?

No. While the thread is alive, setDaemon() throws IllegalThreadStateException, and after it has terminated the flag no longer matters. For a virtual thread, setDaemon(false) throws IllegalArgumentException at any time.

8. Conclusion

A daemon thread is a thread the JVM does not wait for. When the last user thread finishes, the JVM runs its shutdown hooks and halts, and daemon threads stop wherever they are, without running their finally blocks. The flag must be set before start(), is inherited by child threads, and has nothing to do with priority.

Virtual threads and the common ForkJoinPool are always daemon, whereas Executors pools use user threads by default. Use daemon threads for background helpers that can be dropped, and keep any work that must complete on user threads or on executors that we close and wait for.

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.