Java Thread Dump Inspector

Reads Java thread dumps — text or JSON — and points at what is wrong.
One HTML file. Runs in your browser.

A service stops responding, a pool takes no new work, two threads deadlock. You take a thread dump and get thousands of lines. This reads them: which pools are saturated, who holds the lock everyone is waiting on, where the threads pile up.

[ Open the inspector ]

No dump at hand? A sample one is built in — open it and look around.

Before using it After using it
A developer at night, head in hands, a raw thread dump on
      the screen The same developer with the inspector open, reporting no
      deadlocks

The dump never leaves your machine

Parsed in the browser. No upload, no backend, no analytics, no external request — not even a font. Save the page and it works offline.


Which dumps it reads

Taken with JDK 8, 17 JDK 21 JDK 25–28
jstack / jcmd Thread.print yesyesyes
jcmd Thread.dump_to_file -format=json no such commandyes, no states in that formatyes, full data
jcmd Thread.dump_to_file -format=plain no such commandrecognised, asks you to re-take as json

Tested on Temurin 17, 21, 25 and 26, Liberica 8, JetBrains Runtime 25, and OpenJDK early-access 27 and 28.

jstack cannot see virtual threads — on any JDK from 21 to 28. Against an application built on them it shows the carrier threads and nothing of what runs on them. That is deliberate: JEP 425 says traditional thread dumps will not be extended to virtual threads, since a flat list does not scale to millions of them.

The dump that does show them is jcmd <pid> Thread.dump_to_file -format=json dump.json — every virtual thread, grouped by its thread container, and written without pausing the application. Load that file here.

It is not a superset of jstack, though: it lists Java threads, so the JVM's own — GC, JIT compiler, VM Thread — are missing from it. For a GC or JIT question, take both.


Tabs

Tab What it shows
FindingsWhat looks wrong, ranked. Start here.
PoolsWhich pools are saturated, and what their busy threads do.
PopulationWhich thread groups exist and how big they are.
Top frameWhere threads sit right now, most crowded first.
StatesState counts, plus a Thread.State reference.
Creation orderThreads in dump order. Leaks show up as a growing tail.
LocksWho waits on which lock, and who holds it.
Stack searchRegex over full stacks.
CompareTwo dumps: what grew, what went away.

Findings

Most checks are generic: a pool with every worker busy, threads piling up in one frame, several waiters on one lock, a deadlock the JVM reported itself.

Checks that match frames of a specific library carry a signature: badge, so a library-specific guess never reads as a universal truth. Only HikariCP ships built in — narrower ones are pasteable rules, below.


Custom rules

Add your own without touching the source. Findings > Custom rules > + New rule. Write the body of function (d, helpers):

var starved = d.threads.filter(function (t) {
  return t.state === "BLOCKED" && t.top.indexOf("com.acme.cache.") === 0;
});
if (starved.length < 5) return [];

return [{
  level: "alert",
  title: nThreads(starved.length) + " blocked on the cache lock",
  text: "Find the holder in the Locks tab first.",
  items: starved
}];

Return one finding, an array of them, or nothing when the dump does not match. Every thread field and helper is listed in the editor, under How to write a rule, and in the full reference.

Rules live in localStorage: your browser only, never sent anywhere. A rule that throws becomes its own finding card instead of breaking the tab. Ready-made ones for specific libraries are in rules/ — paste a file into the form to use it.


Source on GitHub | Contributing a rule | MIT