Skip to content
Java multithreading 6 min read

ThreadGroup

ThreadGroup lets you treat a set of threads as a single unit — useful for bulk operations like interrupting all worker threads at once, setting a shared maximum priority, or catching uncaught exceptions in one place.

What Is a ThreadGroup?

Every Java thread belongs to exactly one ThreadGroup. Thread groups form a tree: each group can have a parent group, and the root of the tree is the system group, which is created by the JVM at startup. Your main method runs in the main group, which is a child of system.

public class GroupInfo {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        ThreadGroup tg = t.getThreadGroup();

        System.out.println("Thread name: " + t.getName());
        System.out.println("Group name: " + tg.getName());
        System.out.println("Parent group: " + tg.getParent().getName());
    }
}
Thread name: main
Group name: main
Parent group: system

Creating a ThreadGroup

You can create a named group, then pass it to the Thread constructor.

public class BasicGroupDemo {
    public static void main(String[] args) {
        ThreadGroup workers = new ThreadGroup("workers");

        Thread t1 = new Thread(workers, () -> System.out.println("Task A in: " + Thread.currentThread().getThreadGroup().getName()), "thread-A");
        Thread t2 = new Thread(workers, () -> System.out.println("Task B in: " + Thread.currentThread().getThreadGroup().getName()), "thread-B");

        t1.start();
        t2.start();
    }
}
Task A in: workers
Task B in: workers

Tip: Always give your ThreadGroup a descriptive name. It shows up in stack traces and monitoring tools, making debugging much easier.

Nested (Child) Groups

Groups can be nested — child groups inherit the maximum priority of their parent and cannot exceed it.

public class NestedGroupDemo {
    public static void main(String[] args) {
        ThreadGroup parent = new ThreadGroup("parent-group");
        ThreadGroup child  = new ThreadGroup(parent, "child-group");

        new Thread(parent, () -> System.out.println("In parent"), "p1").start();
        new Thread(child,  () -> System.out.println("In child"),  "c1").start();

        System.out.println("Child's parent: " + child.getParent().getName());
    }
}
In parent
In child
Child's parent: parent-group

Useful ThreadGroup Methods

MethodWhat it does
getName()Returns the group’s name
getParent()Returns the parent ThreadGroup
activeCount()Estimates the number of active threads (including subgroups)
activeGroupCount()Estimates the number of active subgroups
setMaxPriority(int)Sets the ceiling priority for all threads in this group
getMaxPriority()Returns the maximum allowed priority
interrupt()Interrupts every thread in the group and all subgroups
isDaemon() / setDaemon(boolean)Gets or sets the daemon flag for the group itself
list()Prints the group tree to System.out — handy for debugging
enumerate(Thread[])Copies active thread references into an array
enumerate(ThreadGroup[])Copies active subgroup references into an array

Setting a Maximum Priority

public class PriorityDemo {
    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("capped");
        group.setMaxPriority(4); // no thread here can exceed priority 4

        Thread t = new Thread(group, () -> {
            System.out.println("Actual priority: " + Thread.currentThread().getPriority());
        }, "low-worker");

        t.setPriority(10); // silently capped at 4
        t.start();
    }
}
Actual priority: 4

Note: setMaxPriority affects new threads and lowers existing threads that exceed the ceiling. It cannot raise a thread’s priority above what was already set.

Interrupting All Threads at Once

One of the most practical uses of ThreadGroup is shutting down a batch of threads cleanly.

public class BulkInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup group = new ThreadGroup("batch");

        for (int i = 0; i < 3; i++) {
            final int id = i;
            new Thread(group, () -> {
                try {
                    System.out.println("Worker " + id + " started");
                    Thread.sleep(10_000); // long-running task
                } catch (InterruptedException e) {
                    System.out.println("Worker " + id + " interrupted");
                }
            }, "worker-" + i).start();
        }

        Thread.sleep(100); // let workers start
        group.interrupt();  // interrupt all three at once
    }
}
Worker 0 started
Worker 1 started
Worker 2 started
Worker 0 interrupted
Worker 1 interrupted
Worker 2 interrupted

Listing Active Threads

public class ListThreadsDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup group = new ThreadGroup("demo");

        for (int i = 0; i < 3; i++) {
            new Thread(group, () -> {
                try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }, "t" + i).start();
        }

        System.out.println("Active count: " + group.activeCount());
        group.list(); // prints the tree to System.out
    }
}

Handling Uncaught Exceptions

You can override uncaughtException(Thread, Throwable) in a subclass of ThreadGroup to provide a single error handler for all threads in the group.

class SafeGroup extends ThreadGroup {
    SafeGroup(String name) {
        super(name);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.err.println("[SafeGroup] Thread '" + t.getName()
                + "' crashed: " + e.getMessage());
        // you could log, restart the thread, or alert here
    }
}

public class UncaughtDemo {
    public static void main(String[] args) {
        SafeGroup group = new SafeGroup("safe-workers");

        new Thread(group, () -> {
            throw new RuntimeException("something went wrong");
        }, "risky-thread").start();
    }
}
[SafeGroup] Thread 'risky-thread' crashed: something went wrong

Tip: For a single thread you can also use Thread.setUncaughtExceptionHandler(). The ThreadGroup approach is convenient when you want the same policy applied to many threads automatically. See Multithreading for the broader picture.

ThreadGroup vs Modern Alternatives

ThreadGroup was introduced in Java 1.0 and predates better concurrency utilities. For most new code you should prefer:

NeedModern alternative
Managed pool of threadsThread Pools & Executors
Result from a threadCallable & Future
Lightweight concurrency (Java 21)Virtual Threads
Structured lifecycle managementStructuredTaskScope (Java 21 preview)

ThreadGroup still has niche uses: legacy code maintenance, embedding environments that rely on its uncaughtException hook, or educational exploration of Java’s threading model.

Warning: ThreadGroup.stop(), suspend(), and resume() are deprecated and inherently unsafe — they were deprecated in Java 1.2 and may be removed in a future release. Never call them in new code.

Under the Hood

When the JVM creates a thread it registers it in the group’s internal list. The group stores a simple resizable array of Thread and ThreadGroup references. activeCount() walks this array recursively — it is an estimate because threads may terminate between the count and the actual traversal.

The priority ceiling enforced by setMaxPriority is checked at the OS-level thread mapping: the JVM calls Thread.setPriority clamped to [1, maxPriority], and the OS scheduler then maps Java priorities (1–10) to its own priority levels, which vary by platform.

Uncaught exception dispatch follows this order:

  1. The thread’s own UncaughtExceptionHandler (if set via Thread.setUncaughtExceptionHandler).
  2. The thread’s ThreadGroup (which may cascade to parent groups).
  3. The default handler set via Thread.setDefaultUncaughtExceptionHandler.

Understanding this chain is important when you mix both individual handlers and group-level handlers — the individual handler wins and the group’s uncaughtException is not called.

Last updated June 13, 2026
Was this helpful?