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
ThreadGroupa 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
| Method | What 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:
setMaxPriorityaffects 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(). TheThreadGroupapproach 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:
| Need | Modern alternative |
|---|---|
| Managed pool of threads | Thread Pools & Executors |
| Result from a thread | Callable & Future |
| Lightweight concurrency (Java 21) | Virtual Threads |
| Structured lifecycle management | StructuredTaskScope (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(), andresume()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:
- The thread’s own
UncaughtExceptionHandler(if set viaThread.setUncaughtExceptionHandler). - The thread’s
ThreadGroup(which may cascade to parent groups). - 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.
Related Topics
- Multithreading — the foundational overview of threads in Java
- Thread Priority — how Java priority maps to OS scheduler priorities
- Thread Pools & Executors — modern, preferred way to manage large numbers of threads
- Daemon Threads — how daemon status interacts with thread and group lifecycle
- Callable & Future — submit tasks and retrieve results without raw thread management
- Virtual Threads — Project Loom’s lightweight threads in Java 21