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.
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.
From the main menu, select
.In the New Project dialog, select New Project and name the project
JavaPassFromConsole
.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 running 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
From the main menu, select
Ctrl+Alt+Shift+S.In the Project Structure dialog, select Artifacts, click and select .
In the Create JAR from Modules dialog, specify
JavaPassFromConsole
as the Main Class and click OK.From the main menu, select Build for the
. Then selectJavaPassFromConsole:jar
artifact. IntelliJ IDEA builds the artifact to out/artifacts/JavaPassFromConsole_jar/JavaPassFromConsole.jar.
Create a Dockerfile and a Docker run configuration
In the Project tool window, right-click the src directory, point to New and click File.
In the New File dialog, type Dockerfile and press Enter.
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 executes the following command from the /tmp directory inside the container:java -jar JavaPassFromConsole.jarClick in the gutter and select New Run Configuration….
In the Edit Run Configuration dialog, do the following:
Click Modify options and select Context folder. Change the context folder to out/artifacts/JavaPassFromConsole_jar (this is where the JAR artifact is located).
Click Modify options and select Attach to container.
Give the configuration a name (for example,
MyPassApp
).Give a name for the container itself (for example,
MyPassAppContainer
).
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:
.
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.
From the main menu, select
.Click and select Remote JVM Debug.
In the Before launch section, click and select Launch Docker Before Debug.
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.jarMake sure that the ports in the Configure Docker dialog and in the remote debug configuration match. Click OK.
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.
Open the JavaPassFromConsole.java file and set breakpoints at key points in the code:
Select the remote debug configuration in the main toolbar and click .
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 your code, click or resume the execution of the application until it hits the next breakpoint with .
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.