IntelliJ IDEA 2020.3 Help

Debug a Java application using a Dockerfile

You can use IntelliJ IDEA to debug a Java application running in a Docker container. This tutorial describes how to create a Dockerfile for running a simple Java console application inside a Docker container with OpenJDK 8 and then debug it by setting breakpoints in the source code and using a remote debug configuration.

For this tutorial, you will need to connect IntelliJ IDEA to a running Docker daemon as described in Enable Docker support.

Create a sample Java console application

The sample application for this tutorial emulates a console utility to change the password. It asks to enter the login name and the current password. After verifying the credentials, it asks to type the new password twice. If the passwords don't match, it asks for the new password again, until they match, and then changes the password. The methods for verifying and changing the password are not implemented for the purposes of this tutorial.

  1. Create a new empty Java project that uses JDK 8.

  2. Create the main Java class file JavaPassFromConsole.java in the src directory.

    To do this, in the Project tool window, right-click the src directory, point to New and click Java Class. In the New Java Class dialog, type JavaPassFromConsole and press Enter .

    Paste the following code into the new file:

    import java.io.Console; import java.util.Arrays; public class JavaPassFromConsole { public static void main (String[] args) { Console c = System.console(); String login = c.readLine("Enter your login: "); char[] oldPassword = c.readPassword("Enter your old password: "); if (verify(login, oldPassword)) { boolean match =false; while(!match) { char[] newPassword1 = c.readPassword("Enter your new password: "); char[] newPassword2 = c.readPassword("Enter new password again: "); match = Arrays.equals(newPassword1, newPassword2); if (match) { change(login, newPassword1); c.format("Password for %s changed.%n", login); } else { c.format("Passwords don't match. Try again.%n"); } Arrays.fill(newPassword1, ' '); Arrays.fill(newPassword2, ' '); } } Arrays.fill(oldPassword, ' '); } // Method for verifying the password static boolean verify(String login, char[] password) { return true; } // Method for changing the password static void change(String login, char[] password) { } }

If you try to run this application in IntelliJ IDEA, it will throw a NullPointerException. This happens because the IDE runs Java applications with the javaw command: without an associated console. As a result, System.console() returns null. You can try to compile the JavaPassFromConsole.java file and run it using the java command from the command line to see that the code is actually correct.

Build a JAR artifact for the application

  1. From the main menu, select File | Project Structure Ctrl+Alt+Shift+S .

  2. In the Project Structure dialog, select Artifacts, click the Add button and select JAR | From modules with dependencies.

  3. In the Create JAR from Modules dialog, specify JavaPassFromConsole as the Main Class and click OK.

    Add a JAR artifact
  4. From the main menu, select Build | Build Artifacts. Then select Build for the JavaPassFromConsole:jar artifact. IntelliJ IDEA builds the artifact to out/artifacts/JavaPassFromConsole_jar/JavaPassFromConsole.jar.

Create a Dockerfile and a Docker run configuration

  1. In the Project tool window, right-click the src directory, point to New and click File.

  2. In the New File dialog, type Dockerfile and click OK.

  3. Paste the following code to the new Dockerfile:

    FROM openjdk:8 COPY . /tmp WORKDIR /tmp CMD ["java", "-jar", "JavaPassFromConsole.jar"]

    These instructions tell Docker to build an image based on openjdk:8, which contains the Java 8 JDK. It copies the contents of the current directory (we will set it to be the artifact output directory) to /tmp inside the container. When you run the container from this image, Docker sets the executes the following command from the /tmp directory inside the container:

    java -jar JavaPassFromConsole.jar
  4. Click the Run button in the gutter and select New Run Configuration.

  5. In the Edit Run Configuration dialog, give the configuration a name (for example, MyPassApp) change the context folder to out/artifacts/JavaPassFromConsole_jar (this is where the JAR artifact is located), and give a name for the container itself (for example, MyPassAppContainer).

    Docker run configuration from Dockerfile
  6. Click Run to check that the application works correctly inside a Docker container. When the container starts, select it in the Services tool window and open the Attached Console tab. You should see the prompt with the words Enter your login:.

    The Services tool window with a running container

Create a remote debug configuration

To attach the debugger to the running application, you need a remote debug configuration. Before starting, the remote debug configuration should first launch the Docker run configuration and start the application in debug mode.

  1. From the main menu, select Run | Edit Configurations.

  2. Click the Add New Configuration button and select Remote JVM Debug.

  3. In the Before launch section, click the Add button and select Launch Docker Before Debug.

  4. Select the Docker configuration that runs your app (MyPassApp) and specify the command to use when running your app in the Custom Command field. The remote debug configuration will use this custom command instead of the one defined in the Dockerfile. This command should contain the -agentlib option to let the debugger attach to the process:

    java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -jar JavaPassFromConsole.jar
    Remote debug configuration with Docker configuration

    Make sure that the ports in the Configure Docker dialog and in the remote debug configuration match. Click OK.

  5. Click OK in the Run/Debug Configurations dialog.

Set breakpoints and debug your application

By this step, you should have the following:

  • The source code of your application: JavaPassFromConsole.java

  • The built JAR artifact: out/artifacts/JavaPassFromConsole_jar/JavaPassFromConsole.jar

  • The Docker run configuration that runs the application in a container based on the Dockerfile

  • The remote debug configuration that first launches the Docker run configuration with a custom command and then attaches to the application in the running container

Before starting the debug configuration, you need to set breakpoints in the source code.

  1. Open the JavaPassFromConsole.java file and set breakpoints at key points in the code:

    Set breakpoints
  2. Select the remote debug configuration in the main toolbar and click the Debug button.

    Remote debug configuration selected

    Alternatively, you can press Alt+Shift+F9 and select the remote debug configuration.

It should now rebuild the image from the Dockerfile and start the container with the application in debug mode. The debugger should attach to the running application and show you the Debug tool window. When the application hits the first breakpoint (at the Enter your login: prompt), the debugger will suspend the application and you can analyze the current state. To step over to the next statement in you code, click The Step Over button or resume the execution of the application until it hits the next breakpoint with the Resume button.

To interact with the application, you need to switch to the Services tool window, select the container under Docker, and open the Attached Console tab.

Last modified: 06 January 2021