Watching Java threads in real time with NetBeans debugger

A misbehaving thread can quietly drain an otherwise healthy Java service, and the symptoms often show up in production long before they appear in the IDE. NetBeans ships with a thread-aware debugger that lets you pause the JVM, inspect every running thread and watch state transitions as they happen.

In Brisbane and Sydney engineering teams, this visibility has become part of the daily routine. Developers pair the debugger with local JUnit runs during the morning standup, then pull annotated thread dumps from staging during afternoon reviews. The workflow also helps when a Perth engineer needs to reproduce an issue reported by a Melbourne client, because snapshots can be exported without losing the triggering stack frames.

Many Australian shops running Java EE workloads rely on NetBeans to validate session bean behaviour under load and to confirm that message-driven beans drain their queues without leaving ghost threads behind. The debugger does not require a commercial licence, which matters for small consultancies in Adelaide and Hobart that prefer the Apache-licensed tooling.

By default, the IDE shows threads as flat rows of name, state and suspend flag. A few small adjustments turn that list into a live dashboard, with suspended-only breakpoints, step filters and a side-by-side source view being the levers most teams reach for first.

Preparing the project for thread-aware debugging

The project must be compiled with debug information and launched in debug mode. If you are inheriting code from another developer, importing that source folder is the fastest way to get a working debug configuration without rebuilding the build script.

Once the project is open, drop a breakpoint anywhere a thread of interest is likely to pass through. A common pattern in Australian retail backends is to set it inside the order-fulfilment service right after the JDBC call, so the suspend point catches both happy-path and timeout scenarios. Switching breakpoint properties to suspend only the thread that hit it keeps the rest of the application responsive.

Capturing state transitions without freezing the application

A full JVM suspension is fine for demos, but production-like environments demand more care. NetBeans lets you set suspend policy per breakpoint, so a single thread can pause while peers keep running. Combine that with conditional breakpoints to filter noise, for example only suspending when a particular user identifier is passed in.

Step filters are equally valuable. Excluding calls into the JDK collection classes or the JDBC driver keeps the call stack focused on your own code. Many teams in Melbourne's fintech corridor rely on this trick when debugging pooling-related latency spikes.

Reading the threads window

When a breakpoint fires, the Debugging window opens with the Threads tab in focus. Each row shows the thread name, priority, daemon flag and current state, ranging from RUNNABLE to BLOCKED, WAITING and TIMED_WAITING. Hovering reveals the call stack, and double-clicking jumps the editor to the frame in scope.

Colour coding is subtle but consistent. Threads holding a monitor on which another thread is waiting are highlighted, which makes deadlock situations easy to spot. For session bean pools, this is often the first clue that a bean is hanging onto a connection it never released.

Sorting by state groups threads blocked on the same monitor, turning the window into a quick triage tool. Right-clicking and choosing Copy Stack produces a snippet that drops cleanly into a ticket.

Hunting deadlocks and resource starvation

Deadlocks rarely show up in unit tests, because they depend on timing. The threads window makes them visible by listing the monitors each thread owns and waits for, which the IDE traces automatically. When two threads appear in a cycle, NetBeans prints a warning in the debugging console.

For message-driven beans that occasionally stop draining queues, the same view can reveal a thread parked indefinitely on a semaphore. Pairing this with log timestamps usually points to a single misbehaving consumer. In one Adelaide logistics firm, this technique uncovered a reconnect loop that had been silently starving the listener thread for weeks.

Leaving the debugger attached during a staging soak test, even without an active breakpoint, lets the threads window refresh in the background. Any thread drifting into BLOCKED for longer than a few seconds stands out, catching starvation long before it becomes customer-facing.

Combining thread inspection with profiling

Thread states alone tell only part of the story. To see why a thread spends so long in RUNNABLE, the NetBeans profiler can be attached to the same debug session. CPU samples are linked back to call sites, so a hot loop in a parsing routine becomes obvious next to a thread that is supposed to be idle.

Where things get tangled, viewing the dependency graph helps confirm that classes involved in a deadlock are even supposed to know about each other. If the graph shows circular references, refactoring is safer than patching the symptom. Memory profiling adds a third dimension: a thread blocked on a connection while heap usage climbs is often leaking sessions, and the dominator tree quickly points to the offending bean.

Attaching to remote JVMs hosted in Australia

Local debugging covers development, but the real test is staging. NetBeans supports attaching to remote JVMs through JDWP, which works against instances in the AWS Sydney region or on-premise racks in Canberra data centres. The attach dialog accepts host, port and shared secret, and once connected behaves exactly like a local session.

For services that talk to databases, managing JDBC connections through the Services tab keeps credentials aligned with what the application actually uses. This matters for compliance with the Notifiable Data Breaches scheme, since mirrored test data must not leak through careless connection strings.

The JDWP port should be firewalled off, and debug credentials should rotate alongside the rest of the secret store. Australian privacy law treats session identifiers as personal information, so any debug log capturing them should be scrubbed before sharing overseas.

Saving snapshots and building better habits

Once a debug session finishes, the thread dump can be exported as plain text or XML. Saving snapshots into the ticket gives the next developer something reproducible to work from. Teams often paste dumps with Australian Eastern Standard Time stamps to avoid confusion during after-hours incidents.

A monthly review of saved dumps, even when no bug was filed, surfaces useful patterns. Repeated BLOCKED states on the same monitor are an early warning that a lock is too coarse, and the fix is cheaper when caught this way.

Habits worth locking in:

  • Suspend only the thread that hits a breakpoint so peer threads keep running.
  • Apply step filters to hide framework internals from the call stack.
  • Export thread dumps into the bug ticket instead of screenshots.
  • Re-run the same scenario under the profiler when RUNNABLE time looks suspicious.
  • Keep remote debug credentials out of source control to align with the Australian Privacy Principles.