Derik Lima's Blog

How to Debug a Java Application running in Docker

By Derik Lima on Dec 20, 2024

Debug Spring Boot Applications running in Docker

I’ve been trying to get back into blogging and plan to write more concise posts that are helpful without taking up too much of your time.

In this post, I’ll briefly explain how to debug a remote Spring Boot application running inside a Docker container.

Step 1: Create a Spring App using Spring Initializr

Link: https://start.spring.io/

Dependencies:

Now, open this project in your preferred IDE.

Step 2: Let’s code a simple Rest Controller

package com.deriklima.postsloader;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@RequestMapping("/posts")
public class PostsloaderApplication {

  public static void main(String[] args) {
    SpringApplication.run(PostsloaderApplication.class, args);
  }

  @GetMapping
  ResponseEntity<List<Post>> posts() {
    List<Post> posts;
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
        .header("Accept", "application/json")
        .GET()
        .build();

    try (HttpClient client = HttpClient.newHttpClient()) {
      HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
      if (response.statusCode() == 200) {
        final ObjectMapper objectMapper = new ObjectMapper();
        posts = objectMapper.readValue(response.body(), new TypeReference<>() {});
      } else {
        return ResponseEntity.status(response.statusCode()).build();
      }
    } catch (IOException | InterruptedException e) {
      throw new RuntimeException(e);
    }
    return ResponseEntity.ok(posts);
  }

  record Post(String id, String userId, String title, String body) {}
}

Step 2.1: Build it

Maven:

./mvnw clean package

Step 2.2: You can now run it

Maven:

./mvnw spring-boot:run

Step 2.3: And call the endpoint you’ve just created

curl -H "Accept: application/json" http://localhost:8080/posts

# If you have "jq" installed, you can pipe the results to it to look nicer
curl -H "Accept: application/json" http://localhost:8080/posts | jq

Step 3: Dockerfile

I’ve placed the Dockerfile in a folder called ‘docker,’ and my commands to build the image will reflect that.

ARG JDK_IMAGE_VERSION=21

FROM openjdk:${JDK_IMAGE_VERSION}

RUN adduser --system postsloader
USER postsloader

COPY --chown=postsloader ./target/*.jar /opt/postsloader/app.jar

WORKDIR /opt/postsloader

# Expose the debug port
EXPOSE 5005
# Expose the application port (default Spring Boot port)
EXPOSE 8080

ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "/opt/postsloader/app.jar"]

Step 3.1: Build the image

Keep in mind that you should be in the root folder of your project before running the command below. Also, you can name it whatever you like (the -t parameter).

docker build -f docker/Dockerfile -t derikjl/postsloader .

Step 3.2: Run the container

docker run --name postsloader -p 8080:8080 -p 5005:5005 derikjl/postsloader

Step 4: Configure Your IDE

For IntelliJ IDEA:

  1. Go to Run > Edit Configurations.
  2. Add a new Remote JVM Debug configuration.
  3. Set the following:
    1. Host: localhost
    2. Port: 5005

For Eclipse:

  1. Go to Run > Debug Configurations.
  2. Add a new Remote Java Application.
  3. Set the Host to localhost and the Port to 5005.

Step 5: Debugging

  1. Add breakpoints in your Spring Boot code.

  2. Call the endpoint we’ve just created

curl -H "Accept: application/json" http://localhost:8080/posts
  1. Inspect variables, evaluate expressions, and step through the code in your IDE when breakpoints are hit

Common Issues

Debugger Cannot Connect

  • Cause: Port 5005 is not exposed or mapped.
  • Solution: Verify the Dockerfile and docker run command.

Breakpoints Not Hit

  • Cause: Code in the container differs from the source code.
  • Solution: Ensure the source and built application are in sync.

Gradle instead of Maven

  • Cause: The Docker image will fail to build because it can’t find the JAR file
  • Solution: The JAR files will be in the build/libs folder instead, you need to adapt your Dockerfile accordingly
© Copyright 2024 by Derik Lima.