Java
Exception Handling
IllegalMonitorStateException
Multithreading
Concurrency

IllegalMonitorStateException on wait call

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

IllegalMonitorStateException on a wait() call means the current thread tried to use an object's monitor methods without owning that object's monitor. In plain Java terms, you can call wait, notify, or notifyAll only while synchronized on the exact same object.

Why the Exception Happens

Every object in Java can act as a monitor. The methods wait(), notify(), and notifyAll() are tied to that monitor, not just to any synchronized block nearby.

This is wrong:

java
1public class BrokenWait {
2    public static void main(String[] args) throws InterruptedException {
3        Object lock = new Object();
4        lock.wait();
5    }
6}

The thread is calling wait() on lock without first entering synchronized (lock), so Java throws IllegalMonitorStateException immediately.

Synchronize on the Same Object You Wait On

The correct pattern is to acquire the monitor of the same object before calling wait().

java
1public class CorrectWait {
2    public static void main(String[] args) throws InterruptedException {
3        Object lock = new Object();
4
5        synchronized (lock) {
6            lock.wait(1000);
7            System.out.println("wait finished");
8        }
9    }
10}

The same rule applies to notify() and notifyAll(). If you are waiting on lock, you must also notify on lock, and both calls must happen while synchronized on lock.

The Real Pattern Is Condition Waiting

wait() is not meant to be used as a generic sleep replacement. It exists for condition waiting. One thread waits until a shared state changes, and another thread changes the state and notifies waiting threads.

java
1public class MessageBox {
2    private final Object lock = new Object();
3    private String message;
4    private boolean ready;
5
6    public void put(String value) {
7        synchronized (lock) {
8            message = value;
9            ready = true;
10            lock.notifyAll();
11        }
12    }
13
14    public String take() throws InterruptedException {
15        synchronized (lock) {
16            while (!ready) {
17                lock.wait();
18            }
19            ready = false;
20            return message;
21        }
22    }
23
24    public static void main(String[] args) {
25        MessageBox box = new MessageBox();
26
27        Thread producer = new Thread(() -> box.put("hello"));
28        Thread consumer = new Thread(() -> {
29            try {
30                System.out.println(box.take());
31            } catch (InterruptedException e) {
32                Thread.currentThread().interrupt();
33            }
34        });
35
36        consumer.start();
37        producer.start();
38    }
39}

This is the real use case for monitor methods: coordination around shared state.

Always Wait in a Loop

Even after you fix the monitor ownership problem, you still need the standard waiting pattern: check the condition in a while loop, not an if block.

That matters because:

  • a thread can wake up without the condition truly being satisfied
  • multiple waiting threads can compete after a notification
  • the condition may have changed again by the time the thread runs

So this is correct:

java
1synchronized (lock) {
2    while (!ready) {
3        lock.wait();
4    }
5}

and this is fragile:

java
1synchronized (lock) {
2    if (!ready) {
3        lock.wait();
4    }
5}

Common Mix-Ups

One common mistake is synchronizing on one object and calling wait() on another.

java
synchronized (this) {
    lock.wait();
}

That still fails, because owning this does not mean owning lock.

Another mistake is using Thread.sleep() and wait() interchangeably. sleep() pauses the current thread without involving a monitor. wait() releases the monitor and participates in inter-thread coordination.

If your code just needs a delay, use sleep. If it needs signaling between threads, use wait and notify, or better yet, use higher-level concurrency utilities when possible.

Common Pitfalls

The first pitfall is calling wait, notify, or notifyAll outside synchronized. That is the direct cause of IllegalMonitorStateException.

Another issue is synchronizing on the wrong object. The monitor you hold must be the same object whose wait-set methods you invoke.

Developers also often use if instead of while around wait(), which introduces correctness bugs even after the exception is gone.

Finally, modern Java often has better tools such as BlockingQueue, CountDownLatch, or Condition. If the coordination problem is nontrivial, those abstractions are usually easier to reason about than low-level monitor code.

Summary

  • 'IllegalMonitorStateException means the thread does not own the monitor of the object it called wait() on.'
  • Call wait, notify, and notifyAll only inside synchronized on the same object.
  • Use wait() for condition waiting, not as a generic sleep mechanism.
  • Always check the wait condition in a while loop.
  • Prefer higher-level concurrency utilities when the coordination logic grows beyond a simple monitor example.

Course illustration
Course illustration

All Rights Reserved.