JavaMultithreadingintermediate
Updated:

Thread Lifecycle in Java: All 6 States Explained

5 min read

Every Java thread moves through six states defined in Thread.State. Learn what triggers each transition and watch the states change in runnable code.

TL;DR – Quick Answer

A Java thread passes through six states defined in the Thread.State enum: NEW (created, not started), RUNNABLE (running or ready to run), BLOCKED (waiting for a monitor lock), WAITING (waiting indefinitely for another thread), TIMED_WAITING (waiting with a timeout) and TERMINATED (finished). You can read the current state anytime with getState().

On This Page

When a production service hangs, the first diagnostic anyone takes is a thread dump — and a thread dump is just a list of threads with their states. If you can read BLOCKED, WAITING and TIMED_WAITING fluently, you can often spot the problem in minutes. That is why the thread lifecycle is asked in almost every Java interview: it tests whether you understand what threads are doing, not just how to create them.

Java defines the lifecycle precisely: the Thread.State enum has exactly six values, and getState() tells you which one a thread is in right now.

The six states at a glance

State Meaning Entered by Leaves when
NEW Created, not started new Thread(...) start() is called
RUNNABLE Running or ready to run start(), or waking from any pause Scheduler decision or a pause
BLOCKED Waiting for a monitor lock Contending for synchronized Lock becomes available
WAITING Paused indefinitely wait(), join(), park() notify(), target dies, unpark()
TIMED_WAITING Paused with a deadline sleep(ms), wait(ms), join(ms) Timeout expires or woken early
TERMINATED Finished run() returns or throws Never — it is final

Two facts about this table earn instant credibility in interviews. First, there is no RUNNING state — Java folds "executing right now" and "ready, waiting for CPU" into RUNNABLE, because the OS scheduler owns that distinction. Second, NEW and TERMINATED are visited exactly once; everything between them can cycle many times per second.

NEW and RUNNABLE: birth and readiness

A thread object begins life as a plain Java object. Until you call start(), no OS thread exists — getState() returns NEW and the task has not run a single instruction. start() does three things: asks the OS for a real thread, moves the state to RUNNABLE, and arranges for run() to be invoked on that new thread.

public class NewToTerminated {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("Task running on " + Thread.currentThread().getName());
        });

        System.out.println("After construction: " + worker.getState()); // NEW
        worker.start();
        worker.join(); // wait for it to finish
        System.out.println("After join: " + worker.getState()); // TERMINATED
    }
}

Run this and you see the two endpoints of every thread's life: NEW before start(), TERMINATED after run() returns. Try adding worker.start() a second time — you get IllegalThreadStateException, proof that the lifecycle is strictly one-way.

Common mistake: A common mistake beginners make is treating a Thread object as reusable. Once terminated, a thread can never run again; you must construct a new one. This is exactly why thread pools exist — they keep threads alive and feed them new tasks instead of dying after one.

BLOCKED: queued at a monitor

A thread is BLOCKED when it tries to enter a synchronized block whose monitor lock another thread currently holds. It is not sleeping and not waiting for a signal — it is queued at the lock and will proceed the instant the lock frees. Here is a program that manufactures the state deliberately:

public class BlockedDemo {
    private static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread holder = new Thread(() -> {
            synchronized (LOCK) {
                pause(1000); // hold the lock for a full second
            }
        });
        Thread contender = new Thread(() -> {
            synchronized (LOCK) {
                System.out.println("contender finally got the lock");
            }
        });

        holder.start();
        Thread.sleep(100);      // let holder grab the lock first
        contender.start();
        Thread.sleep(100);      // give contender time to hit the lock
        System.out.println("contender state: " + contender.getState()); // BLOCKED

        holder.join();
        contender.join();
    }

    private static void pause(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

The output shows contender state: BLOCKED — it wants the monitor that holder owns. In a thread dump, many threads BLOCKED on the same lock is the signature of lock contention, and two threads BLOCKED on each other's locks is a deadlock. Note how the InterruptedException is handled by restoring the interrupt flag — swallowing it silently is a bug pattern covered in exception handling in Java.

WAITING vs TIMED_WAITING: paused on purpose

Both states mean the thread chose to pause; the difference is whether there is a deadline. WAITING threads sleep until another thread acts — notify() after a wait(), the target thread finishing after a join(), or LockSupport.unpark(). TIMED_WAITING threads carry a wake-up alarm: Thread.sleep(500), wait(2000), join(1000).

public class WaitingStates {
    public static void main(String[] args) throws InterruptedException {
        Thread sleeper = new Thread(() -> pause(2000));   // TIMED_WAITING
        sleeper.start();

        Thread joiner = new Thread(() -> {
            try {
                sleeper.join();                            // WAITING (no timeout)
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        joiner.start();

        Thread.sleep(200); // let both threads settle into their pauses
        System.out.println("sleeper: " + sleeper.getState()); // TIMED_WAITING
        System.out.println("joiner:  " + joiner.getState());  // WAITING

        sleeper.join();
        joiner.join();
    }

    private static void pause(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

One thread, two pauses, two different states: sleeper has a deadline so it is TIMED_WAITING; joiner waits open-endedly for sleeper to die, so it is WAITING. Both return to RUNNABLE when their condition resolves — they do not jump straight back onto the CPU, they rejoin the queue.

Interview note: The sharpest follow-up here is "which state releases the lock?" — wait() releases the monitor it was called on (that is its entire purpose), while Thread.sleep() and join() release nothing. A sleeping thread inside a synchronized block still holds the lock and blocks everyone else.

TERMINATED: the end, normal or not

A thread reaches TERMINATED when run() returns normally or when an uncaught exception kills it. The second path surprises people: the exception terminates only that thread, prints a stack trace, and the rest of the program continues. If that thread was doing something critical — consuming a queue, writing a heartbeat — the application limps along half-alive. Register a Thread.UncaughtExceptionHandler (or use an executor, which lets you inspect the failure through the returned Future) so thread deaths never go unnoticed.

Transitions interviewers draw on the whiteboard

The classic diagram question: trace one worker thread through a busy second of its life.

  1. new Thread(task)NEW
  2. start()RUNNABLE (scheduler may run it immediately or not)
  3. Hits a synchronized block owned by another thread → BLOCKED
  4. Gets the lock, calls wait(500) on a condition → TIMED_WAITING (lock released)
  5. Woken by notify() → re-acquires the lock, possibly BLOCKED again briefly → RUNNABLE
  6. run() returns → TERMINATED

Step 5 is the detail that impresses: a notified thread cannot resume inside the synchronized block until it re-acquires the monitor, so WAITING frequently transitions through BLOCKED on the way back. If you can explain that hop, you understand the lifecycle better than most candidates at the 2-years-experience level.

Pro tip: You do not need a debugger to explore this. jstack <pid> (or jcmd <pid> Thread.print) dumps every thread's state in a running JVM. Run it against any Spring Boot app and practice classifying the states you see — that is exactly the skill the lifecycle question is testing. How locks and monitors create these states in the first place is the subject of synchronization in Java.

Frequently Asked Questions

What is the difference between BLOCKED and WAITING in Java?
BLOCKED means the thread wants to enter a synchronized block but another thread holds the monitor lock — it will proceed automatically when the lock frees. WAITING means the thread deliberately paused via wait(), join() or LockSupport.park() and needs another thread to wake it with notify(), completion or unpark().
Is there a RUNNING state in Java?
No. The Thread.State enum has no RUNNING value. A thread that is executing on a CPU and a thread that is ready and waiting for CPU time are both RUNNABLE. The distinction lives in the operating system scheduler, which Java deliberately does not expose.
Can a thread go back to NEW or be restarted after TERMINATED?
No. NEW is entered exactly once at construction, and TERMINATED is permanent. Calling start() on a terminated thread throws IllegalThreadStateException. To run the task again you must create a new Thread object, or better, resubmit the task to a thread pool.
Which methods put a thread into TIMED_WAITING?
Any pause with a time limit: Thread.sleep(millis), wait(timeout), join(timeout), LockSupport.parkNanos() and LockSupport.parkUntil(). When the timeout expires or the thread is woken early, it returns to RUNNABLE and competes for CPU again.
What state is a thread in while performing blocking I/O?
Surprisingly, RUNNABLE. A thread reading from a socket or file is waiting inside the operating system, but the JVM still reports RUNNABLE. This is a classic trick question — BLOCKED refers only to waiting for a synchronized monitor lock, not to I/O.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us