# Debugging a running Java app in Docker

> How to Attach a Debugger to a Running Java Application Inside a Docker Container

May 29, 2024 · 5 min read · https://yasint.dev/debugging-running-java-app-docker/
Tags: java, docker, debugging

---

Debugging a Java application running inside a Docker container can be a straightforward process if you follow these simple steps. In this guide, I will walk you through the necessary steps to attach a debugger to your Java application.

## Java application

First of all, let's create a sample Java application to debug. We'll be using [Javalin](https://javalin.io/) for creating a simple API.

## Project structure

Here is the folder structure for our project:

```txt
.
├── Dockerfile
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── dev/
│   │   │       └── yasint/
│   │   │           └── Main.java
│   │   └── resources
│   └── test/
│       └── java
└── target
```

## Maven configuration (`pom.xml`)

Create the pom.xml file with the following content:

```xml isClosed
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dev.yasint</groupId>
    <artifactId>debugger</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <build>
        <finalName>app</finalName>
    </build>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.javalin</groupId>
            <artifactId>javalin</artifactId>
            <version>6.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.10</version>
        </dependency>
    </dependencies>

</project>
```

## Docker configuration (`Dockerfile`)

Create the Dockerfile with the following content:

```Dockerfile
FROM eclipse-temurin:21-jre-alpine
COPY target/app.jar /app/app.jar
WORKDIR /app
EXPOSE 7070
CMD ["java", "-jar", "app.jar"]
```

## Application code (`Main.java`)

Create the main application file `Main.java` under `src/main/java/dev/yasint/`:

```java
package dev.yasint;

public class Main {

    public static void main(String[] args) {
        Javalin.create()
                .get("/hello", Main::hello)
                .start(7070);
    }

    private static void hello(Context ctx) {
        ctx.result("Hello World!").status(200);
    }

}
```

## Building the application

To build the application, run the following commands:

```bash
mvn clean install package
```

## Running the application in Docker

Now let's build and run the Docker container with our application.
We can use the following commands:

```bash
docker build -t app .
docker run -d -p 7070:7070 --name java_app app
```

Now our application should be running and accessible at http://localhost:7070/hello.

## Attaching the debugger

Now the fun part begins! To enable debugging, we need to modify the Docker configuration to include the necessary JVM options.

## Debugging configuration

Before configuring our IDEs, we need to adjust how we run our Java application. Specifically, we need to enable the [Java Debug Wire Protocol (JDWP)](https://docs.oracle.com/en/java/javase/21/docs/specs/jdwp/jdwp-spec.html). JDWP is the protocol that facilitates communication between a debugger and the Java Virtual Machine (JVM) being debugged (referred to as the target VM)[^1].

I am using JDK `21` and the following option to enable debugging. You can use the same option if you are using JDK `9` or later.

```txt
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
```

However, if you're using a JDK version below `9`, the configuration is slightly different. Below, I outline how to configure various versions of the JDK.

```bash caption="Alternative configs for different Java versions"
// For JDK 5 to 8:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
// For JDK 1.4.x
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
// For 1.3.x and below
-Xnoagent -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
```

The next step is to modify the Dockerfile's `CMD` command to include the debugging configuration:

```bash
CMD ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
```

## Running the application with debugging enabled

Rebuild and run the Docker container with the updated configuration:

```bash
docker stop java_app && docker rmi app --force
docker build -t app .
docker run -d -p 7070:7070 -p 5005:5005 --name java_app app
```

> **Publishing the Debugging Port**
>
> It's crucial to map the debugging port (`5005`) to the host machine, especially since we are running inside a container. This allows the debugger on the host machine to communicate with the JVM inside the container, enabling efficient debugging.

## Configuring your IDE

Now we can set up our IDE to connect to the remote JVM for debugging.

## For IntelliJ IDEA:

1. Go to `Run` > `Edit` Configurations.
2. Click the `+` icon and select `Remote JVM Debug`.
3. Configure the remote debugger with the following settings:
     - **Name**: `MyApp` (literally anything you want)
     - **Host**: `localhost`
     - **Port**: `5005`
4. Apply the changes and click `OK`.

![IntelliJ IDEA remote debugger configuration steps](./intellij-steps.png)

## For Eclipse:

I don't have a detailed explanation for configuring Eclipse, but the steps are quite similar to what I've described above. For a reasonably up-to-date guide, I found this helpful article from Eclipse.org: [Eclipse Debug Configuration](https://www.eclipse.org/community/eclipse_newsletter/2017/june/article1.php).

## Starting the debugging session 🐞

Start the remote debugging session from your IDE. You should now be connected to the JVM running inside your Docker container. You can set breakpoints, inspect variables, and step through your code as if it were running locally.

![Starting the debugging session in IntelliJ](./intellij-run.png "Starting the debugging session in IntelliJ")

## Conclusion

By following these steps, we have successfully created a simple Java application with Javalin, packaged it into a Docker container, and attached a debugger to it. This setup allows for effective debugging, enabling you to troubleshoot and optimize your application efficiently.

[^1]: Note that JDWP is optional and may not be available in all JDK implementations. Its presence allows for the use of the same debugger across different JVMs
