How to Detect Deadlock in Java: A Simple Step-by-Step Guide

You’ve probably been there before: your Java application is running smoothly one minute, then it suddenly freezes completely. No error logs pop up, CPU usage stays low, and nothing you do gets it working again short of a full restart. If this sounds familiar, you’re likely dealing with a deadlock, and knowing how to detect deadlock in Java quickly can save you hours of costly downtime and frustrated support tickets.

Deadlocks happen when two or more threads each hold a lock the other threads need to proceed, and none will release their existing lock first. They’re common in multithreaded Java applications that handle concurrent requests, and they can be tricky to spot if you don’t know what to look for. This guide walks through practical, tested methods you can use to spot deadlocks in both local development and production environments, no expensive third-party tools required.

Common Signs You’re Dealing With a Java Deadlock

Before you dive into running detection tools, it helps to know the red flags that point to a deadlock instead of another performance issue. You don’t want to waste time checking for deadlocks if your problem is just a memory leak or slow database query.

Unresponsiveness that doesn’t resolve on its own and isn’t tied to high CPU or memory usage is the most common first sign of a deadlock. Unlike a memory leak that will slowly eat up RAM and spike CPU, a deadlocked app will often sit idle, since none of the stuck threads are executing any code.

Another key sign is that the issue goes away temporarily after a restart, but comes back after the app has been running for a while, especially under higher than usual load. If restarting your app fixes the issue temporarily but it reoccurs under similar concurrency conditions, you’re almost certainly looking at a deadlock or other concurrency bug. You might also notice that only specific parts of the app are unresponsive, if the deadlock only affects a subset of threads handling certain features.

Built-in JDK Tools to Detect Deadlock in Java

The great news for Java developers is you don’t need to buy any fancy monitoring tools to find deadlocks. Every JDK ships with free, lightweight tools that can detect deadlocks in seconds, whether you’re working on your local machine or debugging a production app.

The most widely used tool is jstack, a command line utility that prints a full thread dump of any running Java process. A thread dump shows you the state of every single thread in your app, what locks they hold, and what locks they’re waiting to acquire. jstack will explicitly flag detected deadlocks at the end of its output, so you don’t have to sift through hundreds of thread stacks manually to find issues.

Using jstack is straightforward, even if you don’t have much experience with command line tools:

  • Run jps (included in the JDK’s bin folder, same as jstack) to get the process ID (PID) of your unresponsive Java application
  • Execute jstack [PID] > thread_dump.txt to save the full thread dump to a text file for easy analysis and sharing with your team
  • Open the text file and scroll to the bottom; look for a section labeled "Found one Java-level deadlock" that lists all affected threads, the locks they hold, and the locks they’re waiting for

If you prefer a graphical interface, jconsole is another built-in JDK tool that lets you connect to a running Java process and check for deadlocks with one click. Just open jconsole, select your running app from the list of processes, navigate to the Threads tab, and click the "Detect Deadlock" button. jconsole is ideal for less technical teams or anyone who wants a visual breakdown of deadlocked threads without parsing text files.

Always save your thread dump to a file instead of viewing it directly in the terminal, so you can reference it later when you’re fixing the root cause of the deadlock. If you’re debugging a production app, you can run these tools without restarting your app, so you won’t lose the state that caused the deadlock in the first place.

Programmatic Deadlock Detection for Ongoing Monitoring

Manual detection with jstack or jconsole works great for ad-hoc debugging, but if you want to catch deadlocks in production before your users report issues, you’ll want to add automated programmatic detection to your app.

The Java platform has a built-in ThreadMXBean class in the java.lang.management package that lets you check for deadlocks directly from your application code. You can write a simple scheduled task that runs every 5 or 10 minutes, calls the findDeadlockedThreads() method, and sends an alert to your engineering team if any deadlocked threads are found. You can also configure it to automatically save a full thread dump when a deadlock is detected, so you have all the data you need to fix the issue right away.

Programmatic detection is perfect for production environments where you want to catch deadlocks before they impact end users, instead of waiting for support tickets to roll in. The overhead of these checks is negligible for most applications, so you don’t have to worry about them affecting performance.

That said, it’s not a perfect fit for every use case. If you’re running a short-lived application that only runs for a few seconds or minutes, the extra code for programmatic detection probably isn’t worth the effort. Don’t rely solely on programmatic detection for local development; manual checks with jstack are faster for testing concurrency fixes as you code.

Common Pitfalls to Avoid During Deadlock Detection

Even with the right tools, it’s easy to make mistakes when you’re looking for deadlocks, especially if you’re in a hurry to fix a production outage. Knowing these common pitfalls will help you avoid wasting time on false positives or missing deadlocks entirely.

The biggest mistake most developers make is taking only one thread dump to check for deadlocks. Threads can be temporarily blocked waiting for a lock for a few milliseconds as part of normal operation, so a single thread dump might show blocked threads that aren’t actually stuck in a permanent deadlock. Single thread dumps can show temporary lock contention that isn’t a real deadlock, so always take multiple dumps spaced 10 to 15 seconds apart to confirm the issue. If the same threads are blocked waiting for the same locks across all your dumps, you have a deadlock.

Another common pitfall is assuming all deadlocks will be detected by JVM tools. If your deadlock involves external resources like database locks, file locks, or native system locks, the JVM won’t be able to detect it, since it only tracks Java-level object locks. If your JDK tools don’t find a deadlock but all the signs point to one, check for locks on external resources your app uses.

Many teams also forget to check for deadlocks during load testing. Most deadlocks only show up under high concurrency, when multiple threads are competing for the same set of locks at the same time. Add deadlock checks to your load testing routine to catch issues before they reach production, instead of scrambling to fix them during a peak traffic outage. You can automate these checks to run as part of your CI/CD pipeline, so you catch concurrency bugs before they’re deployed.

Deadlocks are one of the most frustrating concurrency issues Java developers run into, but they don’t have to be a mystery. With the built-in tools that ship with the JDK and a few simple processes, you can spot and resolve deadlocks in minutes, before they cause major downtime for your users.

Whether you use jstack for quick ad-hoc debugging, jconsole for visual analysis, or add programmatic checks to your production monitoring stack, knowing how to detect deadlock in Java is a critical skill that will save you hours of stress during outages. Next time your Java app freezes unexpectedly, you’ll know exactly what signs to look for and what steps to take to confirm if a deadlock is the root cause.