
A debugger is a powerful tool that allows you to easily inspect the program state in a step-by-step manner. We Java developers are used to debugging programs with the IDE debugger when facing a bug or if we just want to understand how the application works and behaves. Well, that’s if you were able to run the application locally! For a bunch of reasons, sometimes you won’t be able to run the application locally. But then, how can you attach a debugger to your application if you can’t run it locally?
What if you want to inspect the behaviour of an external system that your application depends on and poke around it, but you don’t have access to it on your machine? A remote debugger can do that!
With Java remote debugging, you can attach a debugger to your application whether it’s deployed on a local Kubernetes cluster or a remote one, say on GKE or OpenShift Container Platform, and this is all powered by the Java Debug Wire Protocol.
In this post, we will deploy a Java application on Kubernetes, and we will see how to attach a debugger to a remote JVM.
Whether your cluster is remote or local, the following steps are the same.
Build the image
My Java app is a simple Spring Boot application that provides a REST endpoint:
@RestController
class MyController {
private final Logger LOGGER = LoggerFactory.getLogger(MyController.class);
@GetMapping("/debugme")
public ResponseEntity<String> debugMe() {
LOGGER.info("entering debugMe rest api");
return ResponseEntity.ok("Hello World");
}
}
In my pom.xml
, I’ve added the Jib plugin to build the image:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<from>
<image>openjdk:17</image>
</from>
<to>
<image>docker.io/hamzablm/${project.name}:${project.version}</image>
</to>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
Deploy the image
I’ve created my k8s deployment file to deploy the image:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: debugme
name: debugme
spec:
replicas: 1
selector:
matchLabels:
app: debugme
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: debugme
spec:
containers:
- image: docker.io/hamzablm/debugme:0.0.1-SNAPSHOT
name: debugme
resources: {}
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: debug
containerPort: 5005
protocol: TCP
env:
- name: JAVA_TOOL_OPTIONS
value: '-Xdebug -agentlib:jdwp=transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=n'
In the container ports
section, I’ve exposed two ports in my Pod: One for the spring boot application(8080) and the other for the target VM(5005).
I’ve also added JAVA_TOOL_OPTIONS
environment variable, this will enable the JVM to listen on port 5005
for incoming TCP connection requests from the debugger.
Also, note that I’ve set suspend
to n
since I don’t want to wait for the debugger to attach before executing the main class.
Finally, deploy that image in k8s with kubectl apply -f dep.yaml
Side Note:
If you’re using a CLI utility that manipulates JAVA_TOOL_OPTIONS
environment variable like keytool to import certs, make sure to run in on an initContainer, so the CLI utility doesn’t override JAVA_TOOL_OPTIONS
environment variable that we’ve set.
Debug the application
Now the image is deployed on k8s, we can check the logs with kubectl logs <pod-name>
:

We can see that the pod is started, and the VM is listening on port 5005
.
Forward local port 5005 to the pod’s 5005 port:
kubectl port-forward deploy/debugme 5005:debug
Create “Remote JVM Debug” run configuration:

This config comes with these defaults if you’re on JDK 9 or later:

From the config, you can see that we’re hitting on port 5005
that we’ve exposed locally with the port-forward command.
Now let’s port forward to the Spring Boot application so we can access our application locally:
kubectl port-forward deploy/debugme 8080:http
Now let’s send a request to the app and debug it remotely:
Here I was able to attach my IDE debugger to the VM running on k8s. Of course, I have all the power that comes with a debugger, so I can do advanced stuff like evaluate expressions on the fly, use field watchpoint, use an exception breakpoint, set up conditions in breakpoints etc.
Wrap Up
In this post, we saw how we can attach IntelliJ IDEA’s debugger to a remote JVM process running on k8s.
You can get the demo code in this Github repository.
If you happen to find these articles useful, you can buy me a coffee.