Fundamentals 11 min read

Understanding Java Thread States and Their Transitions with Example Code

This article explains the six Java thread states defined in java.lang.Thread.State, describes the meaning of each state, illustrates the possible state transitions, and provides three runnable code examples that demonstrate NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING and TERMINATED states along with their console outputs.

Java Captain
Java Captain
Java Captain
Understanding Java Thread States and Their Transitions with Example Code

When a thread is created and started in Java, it does not immediately enter the running state; instead, it moves through a series of well‑defined states represented by the java.lang.Thread.State enum. The six possible states are NEW , RUNNABLE , BLOCKED , WAITING , TIMED_WAITING and TERMINATED , each with a specific meaning.

The table below summarizes each state and its description:

Thread State

Specific Meaning

NEW

A thread that has been created but not yet started (the

start()

method has not been called).

RUNNABLE

The thread is eligible to run and is waiting for CPU scheduling; it may be executing or ready to execute.

BLOCKED

The thread is trying to acquire an object lock that is held by another thread.

WAITING

The thread is waiting indefinitely for another thread to perform a specific action (e.g.,

Object.wait()

or

Thread.join()

).

TIMED_WAITING

The thread is waiting for a specified period (e.g.,

Thread.sleep()

,

Object.wait(long)

,

Thread.join(long)

).

TERMINATED

The thread has completed execution and cannot be started again.

Example 1 – Demonstrating TIMED_WAITING

This example prints the thread state sequence NEW → RUNNABLE → TIMED_WAITING → RUNNABLE → TERMINATED using an anonymous inner class and lambda expression.

public class ThreadStateDemo01 {
    public static void main(String[] args) throws InterruptedException {
        // Define an inner thread
        Thread thread = new Thread(() -> {
            System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
            try {
                // Sleep 100 ms
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("4. After Thread.sleep() completes, state: " + Thread.currentThread().getState());
        });
        // State before start
        System.out.println("1. Before start, state: " + thread.getState());
        thread.start();
        // Sleep 50 ms so the thread is still sleeping
        Thread.sleep(50);
        System.out.println("3. While sleeping, state: " + thread.getState());
        // Wait for thread to finish
        Thread.sleep(100);
        System.out.println("5. After execution, state: " + thread.getState());
    }
}

Console output:

1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While sleeping, state: TIMED_WAITING
4. After Thread.sleep() completes, state: RUNNABLE
5. After execution, state: TERMINATED

Example 2 – Demonstrating WAITING

This example shows the transition NEW → RUNNABLE → WAITING → RUNNABLE → TERMINATED by using Object.wait() and Object.notify() .

public class ThreadStateDemo02 {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        Thread thread1 = new Thread(() -> {
            System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
            synchronized (obj) {
                try {
                    Thread.sleep(100);
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("4. After notify(), state: " + Thread.currentThread().getState());
        });
        System.out.println("1. Before start, state: " + thread1.getState());
        thread1.start();
        Thread.sleep(150);
        System.out.println("3. While waiting, state: " + thread1.getState());
        // Another thread to notify
        new Thread(() -> {
            synchronized (obj) {
                obj.notify();
            }
        }).start();
        Thread.sleep(10);
        System.out.println("5. After execution, state: " + thread1.getState());
    }
}

Console output:

1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While waiting, state: WAITING
4. After notify(), state: RUNNABLE
5. After execution, state: TERMINATED

Example 3 – Demonstrating BLOCKED

This example illustrates the sequence NEW → RUNNABLE → BLOCKED → RUNNABLE → TERMINATED by having one thread hold a lock while another attempts to acquire it.

public class ThreadStateDemo03 {
    public static void main(String[] args) throws InterruptedException {
        Object obj2 = new Object();
        // Thread that holds the lock and then waits
        new Thread(() -> {
            synchronized (obj2) {
                try {
                    Thread.sleep(100);
                    obj2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        // Target thread that will be blocked
        Thread thread = new Thread(() -> {
            System.out.println("2. After thread.start(), state: " + Thread.currentThread().getState());
            synchronized (obj2) {
                try {
                    Thread.sleep(100);
                    obj2.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("4. After lock released, state: " + Thread.currentThread().getState());
        });
        System.out.println("1. Before start, state: " + thread.getState());
        thread.start();
        Thread.sleep(50);
        System.out.println("3. While waiting for lock, state: " + thread.getState());
        Thread.sleep(300);
        System.out.println("5. After execution, state: " + thread.getState());
    }
}

Console output:

1. Before start, state: NEW
2. After thread.start(), state: RUNNABLE
3. While waiting for lock, state: BLOCKED
4. After lock released, state: RUNNABLE
5. After execution, state: TERMINATED

Through these three demonstrations, the article confirms that the Java thread states and their transitions described at the beginning are accurate.

JavaconcurrencyThreadmultithreadingExampleThreadState
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.