IntelliJ IDEA 2019.3 Help

Debug asynchronous code

Debugging asynchronous code is a challenge as the execution jumps between frames and makes it harder to follow the code. To help you look back from a specific point in the code, IntelliJ IDEA provides capture points.

A capture point is a place in your code where the debugger captures stack traces to be used later when you reach a specific point in the code (the insertion point) and want to see how you got there. A capture point is specified by the method name (and the containing class) and the key expression. When the debugger stops, it starts matching stack frames with the insertion point, which is another method and expression. If a match is found, the debugger evaluates the expression, and if the value has some related stack information, it replaces the rest of the call stack with the captured stack. This helps you see what was happening at the related capture point, and how you got to the present point.

Asynchronous stack traces are enabled by default (Settings/Preferences | Build, Execution, Deployment | Debugger | Async Stack Traces). The most common capture points are built in, so no configuration is required.

To try async stack traces, debug the following example:

import org.jetbrains.annotations.Async; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class AsyncSchedulerExample { private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); public static void main(String[] args) throws InterruptedException { new Thread(() -> { try { while (true) { process(queue.take()); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); schedule(1); schedule(2); schedule(3); } private static void schedule(@Async.Schedule Integer i) throws InterruptedException { System.out.println("Scheduling " + i); queue.put(i); } private static void process(@Async.Execute Integer i) { // Put a breakpoint here System.out.println("Processing " + i); } }

Use async annotations

If you need capture points that are not included in the default configuration, you can use annotations that can be referenced in your code.

Add a dependency to the JetBrains Maven Java annotations repository for your artifacts and use the @Async.Schedule and @Async.Execute annotations in your code.

When a point annotated with @Async.Schedule is reached, the current stack is saved in a storage map. When a point annotated with @Async.Execute is reached, the debugger tries to find the related stack in the storage map.

You can annotate:

  • methods: this value is used as a key

  • parameters: the parameter value is used as a key

Define custom annotations

If for some reason you do not want to add the JetBrains Maven repository as a dependency for your project, you can define your own annotations and use them instead of the default ones.

  1. Create your own annotations for the capture point and the insertion point (you can use for reference).

  2. In the Settings/Preferences dialog Ctrl+Alt+S, go to Build | Execution | Deployment | Debugger | Async Stack Traces and click Configure annotations.

  3. In the Async Annotations Configuration dialog, click add to add your custom annotations to Async Schedule annotations and Async Execute annotations.

Configure capture points manually

If for some reason you cannot use annotations, or need to be able to capture local variables, you can configure capture points manually.

  1. In the Settings/Preferences dialog Ctrl+Alt+S go to Build, Execution, Deployment | Debugger | Async Stack Traces.

  2. Click add to add a new capture point, and enter the information related to the capture point and the insertion point.

    For example, javax.swing.SwingUtilities.invokeLater with the doRun key will capture all invocations of the invokeLater method and associate them with the Runnable instance parameter. java.awt.event.InvocationEvent.dispatch with the runnable insert key will insert the information captured for invokeLater to the place where Runnable is executed.

  3. (Optional) If you also want to capture local variables (primitives and String values together with the call stack, select the Capture local variables option. Note that this may slow down the debugging process, and that this option is unavailable if the Instrumenting Agent is enabled.

You can download some additional capture settings from the following repository: IntelliJ IDEA debugger Capture Points

View Async Stack Traces in remote JVMs

If you are debugging a remote process, for example managed within a Docker container, you can still use the JVM Instrumenting Agent to display Async Stack Traces as if it were started from the IDE.

To use the agent remotely, do the following:

  • copy <IDEA installation folder>/lib/rt/debugger-agent.jar to any location on the remote machine

  • add -javaagent:<path to debugger-agent.jar> to the remote JVM options

Last modified: 26 April 2020