- Prerequisites
- 1.1 Process vs Thread
- Covers
- NEW · RUNNABLE · BLOCKED · WAITING · TIMED_WAITING · TERMINATED
- Related
- Chapter 3 (lifecycle) · Chapter 4 (monitors) · B2V3 5.2 jstack
On this page
1Concept / Definition
Every Java thread is at any moment in exactly one of six states defined by java.lang.Thread.State: NEW (created, not started), RUNNABLE (executing or ready to execute in the JVM), BLOCKED (waiting to enter a synchronized monitor), WAITING (waiting indefinitely for another thread's action), TIMED_WAITING (waiting with a timeout) and TERMINATED (finished). The states are JVM-level abstractions used by Thread.getState(), thread dumps and monitoring tools; they do not map one-to-one to operating-system thread states.

2Why It Exists
Knowing a thread's state is the first step in diagnosing hangs, slowdowns and deadlocks: a pool full of BLOCKED threads indicates lock contention, WAITING threads in a queue indicate idle workers or waits for results, RUNNABLE threads in socket reads indicate slow downstreams. The state model also documents the precise semantics of each blocking method.
3Core Concepts

NEW
A Thread object has been created but start() has not been called. No OS thread exists yet. Calling start() a second time on any thread (in any state after NEW) throws IllegalThreadStateException.
RUNNABLE
- The thread is executing Java code, or is ready and waiting for the OS to give it a CPU.
- It is also RUNNABLE while executing native code — including blocking socket reads, file I/O and many system calls — because the JVM does not know whether native code is waiting.
- Therefore: RUNNABLE ≠ consuming CPU. Combine thread dumps with per-thread CPU (
top -H,cpu=in dumps) to tell busy from blocked in I/O.

BLOCKED
The thread is waiting to acquire an intrinsic monitor — entering a synchronized block or method whose monitor another thread holds — or to re-acquire the monitor after Object.wait() returns. BLOCKED applies only to monitors; threads waiting for java.util.concurrent locks are WAITING (they are parked).

WAITING
Object.wait()without timeout (after releasing the monitor), untilnotify/notifyAllor interruption.Thread.join()without timeout, until the target thread terminates.LockSupport.park()— used byReentrantLock,Condition.await(),BlockingQueue.take(),CountDownLatch.await(),Future.get(), untilunparkor interruption.- Idle pool threads waiting in
take()are WAITING — normal, not a problem.
TIMED_WAITING
Thread.sleep(millis)/sleep(Duration).Object.wait(timeout),Thread.join(timeout)/join(Duration).LockSupport.parkNanos/parkUntil— used bytryLock(timeout),poll(timeout),await(timeout),Future.get(timeout).- Scheduled executor threads between tasks and pool threads with keep-alive timeouts.
TERMINATED
run() has completed, normally or by throwing an exception (after the uncaught-exception handler ran). The OS thread is gone; the Thread object remains as long as it is referenced. join() on a terminated thread returns immediately.
4How It Works Internally
HotSpot tracks a lower-level thread state (in Java, in VM, in native, blocked) for safepoints, and separately maintains the java.lang.Thread status field that getState() reports. Monitor contention sets BLOCKED around ObjectMonitor::enter; parking sets WAITING/TIMED_WAITING around Parker::park. Virtual threads have the same states; a blocked virtual thread is unmounted from its carrier and does not occupy an OS thread.
5APIs / Features / Syntax
Object lock = new Object();
Thread holder = Thread.ofPlatform().start(() -> { synchronized (lock) { sleepQuietly(5_000); } });
Thread.sleep(100);
Thread contender = Thread.ofPlatform().start(() -> { synchronized (lock) { } });
Thread waiter = Thread.ofPlatform().start(() -> LockSupport.park());
Thread.sleep(100);
// TIMED_WAITING (sleeping while holding the lock)
System.out.println(holder.getState());
System.out.println(contender.getState()); // BLOCKED (wants the monitor)
System.out.println(waiter.getState()); // WAITING (parked)
// Counting states across the JVM
Map<Thread.State, Long> byState = Thread.getAllStackTraces().keySet().stream()
.collect(Collectors.groupingBy(Thread::getState, Collectors.counting()));# States in a thread dump (Volume III 5.2)
$ jcmd <pid> Thread.print | grep -o "State: [A-Z_]*" | sort | uniq -c
212 State: RUNNABLE
48 State: TIMED_WAITING
180 State: WAITING
37 State: BLOCKED ← contention on some monitor: find "waiting to lock <0x…>"6Practical Examples
| Pattern | States | Meaning |
|---|---|---|
| request threads piling up on one monitor | many BLOCKED, one RUNNABLE owner | lock contention (Chapter 4) |
| pool idle | workers WAITING in LinkedBlockingQueue.take | no work — normal |
| requests slow, CPU low | RUNNABLE in socketRead0 | slow downstream / missing timeouts |
| all workers TIMED_WAITING in HikariPool.getConnection | connection-pool starvation | pool too small or connection leak |
| thread never finishes | WAITING in join/ get without timeout | add timeouts; investigate target |
7Common Problems & Mistakes
- Reading RUNNABLE as 'working' — it includes blocking native I/O.
- Expecting BLOCKED for
ReentrantLockwaits — they show as WAITING (parked) with 'parking to wait for'. - Treating many WAITING threads as a problem — idle pools look like that.
- Using
getState()for synchronisation logic — states are for monitoring only and change immediately.
8Performance & Production Considerations
- Export thread counts by state (
jvm_threads_states_threads{state=…}) and alert on BLOCKED spikes. - Always use timeouts on join, get, poll and lock acquisition in production paths to avoid indefinite WAITING.
- Name threads so state statistics can be grouped by pool.
Taking state snapshots is cheap but not free. A full thread dump brings the JVM to a safepoint and walks every stack, which pauses all threads for milliseconds with a few hundred threads and longer with thousands, so take dumps on demand or every few seconds during an incident, not continuously. Thread.getAllStackTraces and ThreadMXBean.dumpAllThreads have the same cost. For trends, rely on per-state gauges, which only read the status field, and on JFR events such as jdk.JavaMonitorEnter and jdk.ThreadPark, which record waits above a threshold together with the lock involved and the stack, at low overhead.
9Architecture & Design Considerations
Thread states turn into design rules once you look at what each state means for a shared resource. Every thread in BLOCKED or WAITING holds memory and often a pool slot while doing nothing, so a design that lets threads wait without a bound converts any slow dependency into exhaustion of the caller. Bound every wait with a timeout, size pools against downstream capacity rather than against expected load, and isolate dependencies in separate pools or bulkheads so that one slow service cannot leave every request thread waiting.
Contention shows up as BLOCKED or parked threads on one lock, and it is a design smell rather than a tuning problem: shrink critical sections, partition state, or replace a shared lock with concurrent structures or message passing. With virtual threads, WAITING becomes cheap because a parked virtual thread releases its carrier, which favours simple blocking code; the limits then move to connection pools and downstream rate limits, which need explicit semaphores. Finally, make states observable by design: name every pool, export per-state counts per pool and alert on sustained BLOCKED growth.
10Interview Questions & Answers
What are the six Java thread states?
java.lang.Thread.State defines six states, and a thread is in exactly one at any moment. NEW means the Thread object exists but start() has not been called. RUNNABLE means it is executing in the JVM or ready to run, which includes native code such as blocking socket reads. BLOCKED means it is waiting to enter a synchronized monitor held by another thread. WAITING means it is waiting without a timeout for another thread, through wait, join or park. TIMED_WAITING is the same with a timeout, including sleep. TERMINATED means run() has finished. These are JVM-level states reported by getState() and thread dumps; they do not map one to one onto OS thread states.
What is the difference between BLOCKED and WAITING?
BLOCKED is narrow: the thread is trying to enter a synchronized block or method, or to re-enter the monitor after Object.wait returns, and another thread holds that monitor. WAITING is broader: the thread has voluntarily suspended itself until another thread acts, through Object.wait without a timeout, Thread.join, or LockSupport.park. Because ReentrantLock, Condition, BlockingQueue, CountDownLatch and Future.get all park, a thread waiting for any java.util.concurrent lock shows as WAITING, not BLOCKED. In a thread dump, BLOCKED appears with waiting to lock and a monitor address, while a parked thread shows parking to wait for and the synchronizer object. Both states mean the thread is not running, but the cause differs.
Which state does Thread.sleep put a thread in?
Thread.sleep puts the thread in TIMED_WAITING until the time elapses or it is interrupted, in which case it throws InterruptedException and clears the interrupt flag. The trap is that sleep does not release anything the thread holds. If it sleeps inside a synchronized block, other threads trying to enter that monitor sit in BLOCKED for the whole sleep, and the same applies to a held ReentrantLock, whose waiters show as WAITING. Object.wait(timeout) is different: it also results in TIMED_WAITING, but it releases the monitor while waiting and must re-acquire it afterwards. So never sleep while holding a lock, and use a condition or wait with a timeout instead.
Why is a thread blocked in a socket read shown as RUNNABLE?
Because the JVM does not know what native code is doing. A blocking socket read in a platform thread ends in a native call such as read or poll, and HotSpot only records that the thread is in native. Thread.State is defined in terms of Java-level waiting, through monitors, wait, join and park, so a thread inside native code is reported as RUNNABLE whether it is computing or sitting in the kernel. That makes RUNNABLE a poor proxy for busy. To tell the difference, check per-thread CPU time with top -H or the cpu field in thread dumps, and set read timeouts on every client so these threads cannot wait forever.
What state is a thread in right after wait() returns due to notify?
It goes to BLOCKED first, not RUNNABLE. Object.wait releases the monitor and puts the thread in the monitor's wait set, in WAITING or TIMED_WAITING. notify or notifyAll moves it from the wait set towards the entry set, but the notifying thread still holds the monitor, typically until it leaves its synchronized block. The woken thread must re-acquire the monitor before wait can return, so until then it competes with other threads and reports BLOCKED. Once it wins the monitor it becomes RUNNABLE and continues after the wait call. This is why waits belong in a loop that re-checks the condition: another thread may have changed it in between.
How can thread states help diagnose production issues?
I take several thread dumps a few seconds apart and compare them, because one dump is only a snapshot. Counting threads by state and grouping them by pool name and top frames turns a dump into a diagnosis. Many BLOCKED threads waiting on the same monitor, with one RUNNABLE owner, mean lock contention. Workers in TIMED_WAITING inside a connection pool's getConnection mean pool starvation or a leak. RUNNABLE threads stuck in socketRead or in native frames with low CPU mean a slow downstream or missing timeout. WAITING threads in join or Future.get without a timeout point to an indefinite wait. Per-state metrics then show when the pattern started.
Why should production code use timed waits?
Because an indefinite wait hands control of your thread to someone else. If a dependency hangs or a result never arrives, a thread in join, Future.get, BlockingQueue.take or a lock acquisition stays WAITING forever, and under load every request thread ends up there, exhausting the pool and making the whole service unresponsive rather than just one feature. A timeout bounds that damage: the thread returns, the caller can fail fast, retry, fall back or report a clear error, and the pool keeps serving other work. Set timeouts from latency budgets, shorter than the caller's own timeout, and combine them with circuit breakers so repeated timeouts stop hitting the failing dependency.