How to Debug a Java Application running in Docker
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:
- Go to Run > Edit Configurations.
- Add a new Remote JVM Debug configuration.
- Set the following:
- Host: localhost
- Port: 5005
For Eclipse:
- Go to Run > Debug Configurations.
- Add a new Remote Java Application.
- Set the Host to localhost and the Port to 5005.
Step 5: Debugging
-
Add breakpoints in your Spring Boot code.
-
Call the endpoint we’ve just created
curl -H "Accept: application/json" http://localhost:8080/posts
- 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