In my previous article, I shared with you the steps I took, to further automate setting up my demo environment, and implementing a Service with service type LoadBalancer.
[Quarkus – Kubernetes extension (reinvestigated, part 5), implementing a Service of service type LoadBalancer with K3s]
Some years ago, I also wrote articles about the Quarkus Kubernetes Extension.
This time I wanted to have a look at some others aspects of the Quarkus Kubernetes Extension, that I came across.
Part of the “Developer Joy” is the so called “Remote Development”.
Run dev mode remotely with changes to local files immediately available in a containerize environment.
Read the Remote Development Mode guide
[https://quarkus.io/developer-joy/]
But first I wanted to have a look at remote debugging.
To remotely debug applications that are running on a Kubernetes environment, we need to deploy the application in a particular way.
[https://quarkus.io/guides/deploying-to-kubernetes#remote-debugging]
In this article, you can read more about the steps I took to remotely debug a Java application that is running in a container on a Kubernetes cluster in my already existing Linux demo environment, including Quarkus and K3s (a lightweight certified Kubernetes distribution) and setup via Vagrant.
In a next article, you can read more about using development mode remotely, so that you can run Quarkus in a container environment and have changes made to your local files become immediately visible.
Remote JVM debugging a Java application directly on my Windows laptop
In order to learn more about the remote JVM debugging part, I had a look at: IntelliJ IDEA tutorial: Remote debug
Below you see the overview of this tutorial (that uses a class named Alphabet):
Remote debugging lets you control and inspect a Java program running on another machine as if it were local.
To do this, you first start the application on the remote computer and configure it to accept debugger connections by adding a special VM option. This option makes the remote JVM wait for incoming connections from a debugger.
Once the remote app is up and listening, you use your local machine to start a debugger in “attach” mode. This connects the IntelliJ IDEA’s debugger to the remote application over the network, allowing you to debug it.
For simplicity, we will launch the debugger and the host application on the same computer. The steps, however, are identical for debugging that is actually remote, so you will be able to use the same procedure for both scenarios.
[https://www.jetbrains.com/help/idea/tutorial-remote-debug.html]
I first wanted to try out the example from that tutorial directly on my Windows laptop using the IntelliJ IDEA (with the Java 25 JDK).
Following the tutorial, I set some breakpoints in the source code of the Alphabet class.
Below you can see the source code of the Alphabet class:
public class Alphabet { public static void main(String[] args) { System.out.println("Starting"); for (char c = 'A'; c < 'Z'; c++) { try { Thread.sleep(2500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(c); } System.out.println("Complete"); } }
As you can see, the application will output the characters A to Y, each with a 2,5 second delay.
Setting up the debugger
Via the menu in IntelliJ IDEA, I navigated to Run | Edit Configurations…| (+) Add New Configuration | Remote JVM Debug.
I created the following Remote JVM Debug configuration (named: localhost5005), for “JDK 9 or later” with the following command line arguments for remote JVM:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
Setting up the application
Via the menu in IntelliJ IDEA, I navigated to Run | Edit Configurations…| (+) Add New Configuration | Application.
I created the following run configuration (named: Alphabet), with the following VM options, copied from the Remote JVM Debug configuration:
Running the application
Then I proceeded with running the application.
From the Alphabet class, via the menu I navigated to Run | Run… (Alt+Shift+F10) | Alphabet:
With the following output:
C:\My\App\Oracle\Java\jdk-25\bin\java.exe "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" "-javaagent:C:\My\App\Tools\JetBrains\IntelliJ IDEA Community Edition 2025.2.3\lib\idea_rt.jar=60989" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\My\Temp\remote-debug\target\classes Alphabet Listening for transport dt_socket at address: 5005 Starting A B
In the output you can see, the following:
Listening for transport dt_socket at address: 5005
This indicates that we have enabled the debug agent and our program is ready to accept incoming debugger connections.
Then as soon as possible, from the Alphabet class, via the menu I navigated to Run | Debug… (Alt+Shift+F9) | localhost5005:
As a result, the program is suspended at a breakpoint, and you can perform any debugging actions like stepping, expression evaluation, and so on.
As you can see the program was at the breakpoint on line 10. This breakpoint is within a for loop. Next, I removed that breakpoint and chose “Resume Program”.
Remark:
Because it took a short some time to start the debugger, the program was already past the breakpoint on line 3.
After a while the program was at the breakpoint on line 12.
Next, I had a look at the tab Console:
The following output was visible in the debugger console:
Connected to the target VM, address: 'localhost:5005', transport: 'socket'
Next, I chose “Resume Program”.
With the following output in the debugger console:
Disconnected from the target VM, address: 'localhost:5005', transport: 'socket'
Next, I closed the tab “localhost5005”.
So, remote JVM debugging a Java application directly on my Windows laptop worked.
By the way, the normal output when you run the application is:
C:\My\App\Oracle\Java\jdk-25\bin\java.exe "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" "-javaagent:C:\My\App\Tools\JetBrains\IntelliJ IDEA Community Edition 2025.2.3\lib\idea_rt.jar=62062" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\My\Temp\remote-debug\target\classes Alphabet Listening for transport dt_socket at address: 5005 Starting A B C D E F G H I J K L M N O P Q R S T U V W X Y Complete Process finished with exit code 0
Remark:
If you start the remote JVM debugger, without starting the application first, you get the following error:
Error running 'localhost5005' Unable to open debugger port (localhost:5005): java.net.ConnectException "Connection refused: connect
Remote JVM debugging a Java application from my Linux demo environment
So, next I wanted to try out remote JVM debugging, by running the “kubernetes-quickstart” application on my Linux demo environment and debugging it on my Windows laptop where I had the source code available in the IntelliJ IDEA.
The base for all of this was my already existing Linux demo environment, including Quarkus and K3s (a lightweight certified Kubernetes distribution) and setup via Vagrant, as described in a previous article.
[Quarkus – Kubernetes extension (reinvestigated, part 5), implementing a Service of service type LoadBalancer with K3s]
For the Linux demo environment to start, from the Oracle VM VirtualBox Manager on my Windows laptop, I started the appliance (in “Headless Start” mode).
Once the VM was running, for executing later manual steps, I used vagrant ssh to connect into the running VM.
In order to get the Java version, I used the following command on the Linux Command Prompt:
java -version
With the following output:
openjdk version "24.0.1" 2025-04-15 OpenJDK Runtime Environment (build 24.0.1+9-30) OpenJDK 64-Bit Server VM (build 24.0.1+9-30, mixed mode, sharing)
On my Windows laptop in the IntelliJ IDEA I opened the source code of the GreetingResource class and set a breakpoint. As you may remember from a previous article, I had a shared folder available.
[Quarkus – Supersonic Subatomic Java – Get Started (reinvestigated)]
Below you can see the source code of the GreetingResource class:
package org.acme;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}
Via the menu in IntelliJ IDEA, I navigated to File | Project Structure… | Project and imported and used the correct SDK: Java OpenJDK 24.0.1.
In order to learn more about the remote JVM debugging part, I had a look at: IntelliJ IDEA tutorial: Remote debug
Setting up the debugger
In the same way as before, via the menu in IntelliJ IDEA, I navigated to Run | Edit Configurations…| (+) Add New Configuration | Remote JVM Debug.
I created the following Remote JVM Debug configuration (named: localhost5005), for “JDK 9 or later” with the following command line arguments for remote JVM:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
Running the application
For running the application I opted, on my Linux demo environment, for the Quarkus development mode.
For this, I also had a look at: Quarkus and Maven
By the way, in a previous article “Quarkus – Supersonic Subatomic Java – Get Started (reinvestigated)“, I already described how I used the Quarkus development mode, with the Getting Started Application.
Development mode
Quarkus comes with a built-in development mode. Run your application (for example via Maven) with:
You can then update the application sources, resources and configurations. The changes are automatically reflected in your running application. This is great to do development spanning UI and database as you see changes reflected immediately.
Dev mode enables hot deployment with background compilation, which means that when you modify your Java files or your resource files and refresh your browser these changes will automatically take effect. This works too for resource files like the configuration property file. The act of refreshing the browser triggers a scan of the workspace, and if any changes are detected the Java files are compiled, and the application is redeployed, then your request is serviced by the redeployed application. If there are any issues with compilation or deployment an error page will let you know.
Hit CTRL+C to stop the application.
[https://quarkus.io/guides/maven-tooling#dev-mode]
Debugging
In development mode, Quarkus starts by default with debug mode enabled, listening to port 5005 without suspending the JVM.
This behavior can be changed by giving the debug system property one of the following values:
- false - the JVM will start with debug mode disabled
- true - The JVM is started in debug mode and will be listening on port 5005
- client - the JVM will start in client mode and attempt to connect to localhost:5005
- {port} - The JVM is started in debug mode and will be listening on {port}
An additional system property suspend can be used to suspend the JVM, when launched in debug mode. suspend supports the following values:
- y or true - The debug mode JVM launch is suspended
- n or false - The debug mode JVM is started without suspending
[https://quarkus.io/guides/maven-tooling#debugging]
As you may remember from a previous article, this Quarkus application is running at localhost:8080. In development mode, Quarkus starts by default with debug mode enabled, listening to port 5005 without suspending the JVM.
[Quarkus – Supersonic Subatomic Java – Get Started (reinvestigated)]
So, the first thing I needed was a port 8080 and 5005 forward from my Windows laptop to my VirtualBox Appliance with Ubuntu 22.04.
In the Oracle VM VirtualBox Manager on my Windows laptop, I navigated to the appliance | Details | Network | Advanced | Port Forwarding. Next, I added the two extra port forwarding rules:
Remark:
For now I didn’t add them also to my Vagrantfile.
In order to run the application in development mode, I used the following commands on the Linux Command Prompt:
cd /mnt/mysharedfolder/kubernetes-quickstart/ mvn quarkus:dev
With the following output:
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/apache-maven-3.9.11/lib/guice-5.1.0-classes.jar) WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release [INFO] Scanning for projects... [INFO] [INFO] -------------------< org.acme:kubernetes-quickstart >------------------- [INFO] Building kubernetes-quickstart 1.0.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- quarkus:3.25.2:dev (default-cli) @ kubernetes-quickstart --- [INFO] Invoking resources:3.3.1:resources (default-resources) @ kubernetes-quickstart [INFO] Copying 1 resource from src/main/resources to target/classes [INFO] Invoking quarkus:3.25.2:generate-code (default) @ kubernetes-quickstart [INFO] Invoking compiler:3.14.0:compile (default-compile) @ kubernetes-quickstart [INFO] Nothing to compile - all classes are up to date. [INFO] Invoking resources:3.3.1:testResources (default-testResources) @ kubernetes-quickstart [INFO] skip non existing resourceDirectory /mnt/mysharedfolder/kubernetes-quickstart/src/test/resources [INFO] Invoking quarkus:3.25.2:generate-code-tests (default) @ kubernetes-quickstart [INFO] Invoking compiler:3.14.0:testCompile (default-testCompile) @ kubernetes-quickstart [INFO] Nothing to compile - all classes are up to date. Listening for transport dt_socket at address: 5005 2025-10-16 17:55:16,080 INFO [io.qua.kub.dep.PropertyUtil] (build-10) Kubernetes manifests are generated with 'The container port http' having default value '8080'. The app and manifests will get out of sync if the property 'quarkus.http.port' is changed at runtime. __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2025-10-16 17:55:17,910 INFO [io.quarkus] (Quarkus Main Thread) kubernetes-quickstart 1.0.0-SNAPSHOT on JVM (powered by2025-10-16 17:55:17,913 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2025-10-16 17:55:17,915 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, compose, kubernetes, rest, smallrye-context-propagation, vertx] -- Tests paused Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>
Next, I pressed r to resume testing.
With the following output:
All 1 test is passing (0 skipped), 1 test was run in 4260ms. Tests completed at 17:55:36. Press [e] to edit command line args (currently ''), [r] to re-run, [o] Toggle test output, [:] for the terminal, [h] for more options>
In the output you can see, the following:
Listening for transport dt_socket at address: 5005
This indicates that we have enabled the debug agent and our program is ready to accept incoming debugger connections.
In IntelliJ IDEA on my Windows laptop, from the GreetingResource class, via the menu I navigated to Run | Debug… (Alt+Shift+F9) | localhost5005:
So, now I got the following error:
Unable to open debugger port (localhost:5005): java.net.SocketException "Connection reset"
Then, in the Web Browser on my Windows laptop, I entered the URL:
And I got the following result:
This site can’t be reached
This looked all familiar to me. I remember from a previous article I wrote, that I had to make Quarkus listen on all network interfaces to resolve this.
[Quarkus – Supersonic Subatomic Java – Get Started (reinvestigated)]
In that article I described that I added the following setting in file application.properties:
[https://stackoverflow.com/questions/55043764/how-to-make-quarkus-to-listen-on-all-network-interfaces-instead-of-localhost]
quarkus.http.host=0.0.0.0
This time however, I was going to use a command line parameter.
Also remember the “Development mode” paragraph from the Quarkus and Maven documentation:
By default, quarkus:dev sets the debug host to localhost (for security reasons). If you need to change this, for example to enable debugging on all hosts, you can use the -DdebugHost option like so:
./mvnw quarkus:dev -DdebugHost=0.0.0.0
[https://quarkus.io/guides/maven-tooling#dev-mode]
So, in the still running session, I pressed q to quit the application.
With the following output:
2025-10-16 18:09:24,281 INFO [io.quarkus] (Quarkus Main Thread) code-with-quarkus stopped in 0.005s -- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 14:07 min [INFO] Finished at: 2025-10-16T18:09:24Z [INFO] ------------------------------------------------------------------------
In order to run the application in development mode (and listening on all network interfaces), I used the following commands on the Linux Command Prompt:
cd /mnt/mysharedfolder/kubernetes-quickstart/ mvn quarkus:dev -Dquarkus.http.host=0.0.0.0 -DdebugHost=0.0.0.0 -Dsuspend
Remark about suspend:
Whether the JVM launch, in debug mode, should be suspended. This parameter is only relevant when the JVM is launched in debug mode. This parameter supports the following values (all the allowed values are case-insensitive):
Value | Effect |
y or true | The debug mode JVM launch is suspended |
n or false | The debug mode JVM is started without suspending |
[https://quarkus.io/guides/quarkus-maven-plugin]
With the following output:
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/apache-maven-3.9.11/lib/guice-5.1.0-classes.jar) WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release [INFO] Scanning for projects... [INFO] [INFO] -------------------< org.acme:kubernetes-quickstart >------------------- [INFO] Building kubernetes-quickstart 1.0.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- quarkus:3.25.2:dev (default-cli) @ kubernetes-quickstart --- [INFO] Invoking resources:3.3.1:resources (default-resources) @ kubernetes-quickstart [INFO] Copying 1 resource from src/main/resources to target/classes [INFO] Invoking quarkus:3.25.2:generate-code (default) @ kubernetes-quickstart [INFO] Invoking compiler:3.14.0:compile (default-compile) @ kubernetes-quickstart [INFO] Nothing to compile - all classes are up to date. [INFO] Invoking resources:3.3.1:testResources (default-testResources) @ kubernetes-quickstart [INFO] skip non existing resourceDirectory /mnt/mysharedfolder/kubernetes-quickstart/src/test/resources [INFO] Invoking quarkus:3.25.2:generate-code-tests (default) @ kubernetes-quickstart [INFO] Invoking compiler:3.14.0:testCompile (default-testCompile) @ kubernetes-quickstart [INFO] Nothing to compile - all classes are up to date. Listening for transport dt_socket at address: 5005
In IntelliJ IDEA on my Windows laptop, from the GreetingResource class, via the menu I navigated to Run | Debug… (Alt+Shift+F9) | localhost5005:
The following output was visible in the debugger console:
Connected to the target VM, address: 'localhost:5005', transport: 'socket'
This triggered the application to continue with running:
2025-10-17 10:56:31,910 INFO [io.qua.kub.dep.PropertyUtil] (build-4) Kubernetes manifests are generated with 'The container port http' having default value '8080'. The app and manifests will get out of sync if the property 'quarkus.http.port' is changed at runtime. __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2025-10-17 10:56:34,963 INFO [io.quarkus] (Quarkus Main Thread) kubernetes-quickstart 1.0.0-SNAPSHOT on JVM (powered by2025-10-17 10:56:34,974 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2025-10-17 10:56:34,975 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, compose, kubernetes, rest, smallrye-context-propagation, vertx] -- Tests paused Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>
Then, in the Web Browser on my Windows laptop, I entered the URL:
This triggered the debugger:
As you can see the program was at the breakpoint on line 14. Next, I chose “Resume Program”.
And, then I got the following expected result:
Next, I pressed r to resume testing.
With the following output:
--
Running 1/1. Running: org.acme.GreetingResourceTest#testHelloEndpoint()
Press [e] to edit command line args (currently ''), [o] Toggle test output, [:] for the terminal, [h] for more options>
Again, this triggered the debugger. So again in IntelliJ IDEA, I chose “Resume Program”.
With the following output:
2025-10-17 11:10:35,383 ERROR [io.qua.test] (Test runner thread) ==================== TEST REPORT #1 ==================== 2025-10-17 11:10:35,384 ERROR [io.qua.test] (Test runner thread) Test GreetingResourceTest#testHelloEndpoint() failed : java.net.SocketTimeoutException: Read timed out at io.restassured.internal.RequestSpecificationImpl.get(RequestSpecificationImpl.groovy) at org.acme.GreetingResourceTest.testHelloEndpoint(GreetingResourceTest.java:14) 2025-10-17 11:10:35,386 ERROR [io.qua.test] (Test runner thread) >>>>>>>>>>>>>>>>>>>> Summary: <<<<<<<<<<<<<<<<<<<< org.acme.GreetingResourceTest#testHelloEndpoint(GreetingResourceTest.java:14) GreetingResourceTest#testHelloEndpoint() Read timed out 2025-10-17 11:10:35,387 ERROR [io.qua.test] (Test runner thread) >>>>>>>>>>>>>>>>>>>> 1 TEST FAILED <<<<<<<<<<<<<<<<<<<< -- 1 test failed (0 passing, 0 skipped), 1 test was run in 132400ms. Tests completed at 11:10:35. Press [e] to edit command line args (currently ''), [r] to re-run, [o] Toggle test output, [:] for the terminal, [h] for more options>
Because of a read time out the test failed, as one might expect.
So, in the still running session, I pressed q to quit the application.
With the following output:
-- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 17:25 min [INFO] Finished at: 2025-10-17T11:12:20Z [INFO] ------------------------------------------------------------------------
Next, in IntelliJ IDEA, I closed the tab “localhost5005”.
So, remote JVM debugging a Java application from my Linux demo environment also worked 😊.
Remote JVM debugging a Java application from a Kubernetes cluster running in my Linux demo environment
From the Quarkus Kubernetes Extension documentation, I had a look at the “Remote Debugging" paragraph.
[https://quarkus.io/guides/deploying-to-kubernetes]
Remote debugging
To remotely debug applications that are running on a kubernetes environment, we need to deploy the application as described in the previous section and add as new property: quarkus.kubernetes.remote-debug.enabled=true. This property will automatically configure the Java application to append the java agent configuration (for example: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005) and also the service resource to listen using the java agent port.
[https://quarkus.io/guides/deploying-to-kubernetes#remote-debugging]
Next, on my Windows laptop, in my shared folder, I navigated to kubernetes-quickstart\src\main\resources and changed the content of file application.properties to:
[in bold, I highlighted the changes]
quarkus.container-image.registry=localhost:8443
quarkus.container-image.username=mylocregusername
quarkus.container-image.password=mylocregpassword
quarkus.container-image.group=quarkus
quarkus.kubernetes.namespace=nl-amis-development
quarkus.kubernetes.ports."ports".container-port=8080
quarkus.kubernetes.ports."ports".host-port=8180
quarkus.kubernetes.ports."ports".node-port=30010
quarkus.kubernetes.replicas=3
quarkus.kubernetes.service-type=node-port
quarkus.kubernetes.remote-debug.enabled=true
quarkus.kubernetes.remote-debug.address-port=5005
As, you can see I also opted for returning to using a service of service type NodePort.
[Quarkus – Kubernetes extension (reinvestigated, part 4), implementing a Service of service type NodePort]
Remark about quarkus.kubernetes.remote-debug.enabled:
Kubernetes property | Type | Default |
quarkus.kubernetes.remote-debug.enabled If true, the debug mode in pods will be enabled. Environment variable: QUARKUS_KUBERNETES_REMOTE_DEBUG_ENABLED | boolean | false |
[https://quarkus.io/guides/all-config]
Remark about quarkus.kubernetes.remote-debug.address-port:
Kubernetes property | Type | Default |
quarkus.kubernetes.remote-debug.address-port It specifies the address at which the debug socket will listen. Environment variable: QUARKUS_KUBERNETES_REMOTE_DEBUG_ADDRESS_PORT | int | 5005 |
[https://quarkus.io/guides/all-config]
So, although I used the default value, I still opted for adding quarkus.kubernetes.remote-debug.address-port to the application.properties.
In order to recreate the Kubernetes manifests, I used the following commands on the Linux Command Prompt:
cd /mnt/mysharedfolder/kubernetes-quickstart mvn clean install
Below, you can see the content of the target/kubernetes/kubernetes.yml Kubernetes manifests, provided by the Quarkus project packaging:
[in bold, I highlighted the changes with regard to remote debugging]
--- apiVersion: v1 kind: Service metadata: annotations: app.quarkus.io/quarkus-version: 3.25.2 app.quarkus.io/build-timestamp: 2025-10-17 - 17:04:59 +0000 labels: app.kubernetes.io/name: kubernetes-quickstart app.kubernetes.io/version: 1.0.0-SNAPSHOT app.kubernetes.io/managed-by: quarkus name: kubernetes-quickstart namespace: nl-amis-development spec: ports: - name: debug port: 5005 protocol: TCP targetPort: 5005 - name: ports nodePort: 30010 port: 8180 protocol: TCP targetPort: 8080 selector: app.kubernetes.io/name: kubernetes-quickstart app.kubernetes.io/version: 1.0.0-SNAPSHOT type: NodePort --- apiVersion: apps/v1 kind: Deployment metadata: annotations: app.quarkus.io/quarkus-version: 3.25.2 app.quarkus.io/build-timestamp: 2025-10-17 - 17:04:59 +0000 labels: app.kubernetes.io/name: kubernetes-quickstart app.kubernetes.io/version: 1.0.0-SNAPSHOT app.kubernetes.io/managed-by: quarkus name: kubernetes-quickstart namespace: nl-amis-development spec: replicas: 3 selector: matchLabels: app.kubernetes.io/version: 1.0.0-SNAPSHOT app.kubernetes.io/name: kubernetes-quickstart template: metadata: annotations: app.quarkus.io/quarkus-version: 3.25.2 app.quarkus.io/build-timestamp: 2025-10-17 - 17:04:59 +0000 labels: app.kubernetes.io/managed-by: quarkus app.kubernetes.io/version: 1.0.0-SNAPSHOT app.kubernetes.io/name: kubernetes-quickstart spec: containers: - env: - name: KUBERNETES_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: JAVA_TOOL_OPTIONS value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" image: localhost:8443/quarkus/kubernetes-quickstart:1.0.0-SNAPSHOT imagePullPolicy: Always name: kubernetes-quickstart ports: - containerPort: 5005 name: debug protocol: TCP - containerPort: 8080 name: ports protocol: TCP
Next, in order to build and push a container image for my project, I used the following commands on the Linux Command Prompt:
cd /mnt/mysharedfolder/kubernetes-quickstart mvn clean install -Dquarkus.container-image.build=true -Dquarkus.container-image.push=true
With the following output:
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/apache-maven-3.9.11/lib/guice-5.1.0-classes.jar) WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release [INFO] Scanning for projects... [INFO] [INFO] -------------------< org.acme:kubernetes-quickstart >------------------- [INFO] Building kubernetes-quickstart 1.0.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ kubernetes-quickstart --- [INFO] Deleting /mnt/mysharedfolder/kubernetes-quickstart/target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ kubernetes-quickstart --- [INFO] Copying 1 resource from src/main/resources to target/classes [INFO] [INFO] --- quarkus:3.25.2:generate-code (default) @ kubernetes-quickstart --- [INFO] [INFO] --- compiler:3.14.0:compile (default-compile) @ kubernetes-quickstart --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 1 source file with javac [debug parameters release 21] to target/classes [INFO] [INFO] --- quarkus:3.25.2:generate-code-tests (default) @ kubernetes-quickstart --- [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ kubernetes-quickstart --- [INFO] skip non existing resourceDirectory /mnt/mysharedfolder/kubernetes-quickstart/src/test/resources [INFO] [INFO] --- compiler:3.14.0:testCompile (default-testCompile) @ kubernetes-quickstart --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 2 source files with javac [debug parameters release 21] to target/test-classes [INFO] [INFO] --- surefire:3.5.3:test (default-test) @ kubernetes-quickstart --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running org.acme.GreetingResourceTest 2025-10-17 17:13:27,337 INFO [io.quarkus] (main) kubernetes-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.25.2) started in 5.909s. Listening on: http://localhost:8081 2025-10-17 17:13:27,343 INFO [io.quarkus] (main) Profile test activated. 2025-10-17 17:13:27,343 INFO [io.quarkus] (main) Installed features: [cdi, compose, kubernetes, rest, smallrye-context-propagation, vertx] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.861 s -- in org.acme.GreetingResourceTest Exception in thread "vert.x-internal-blocking-1" java.lang.IllegalAccessError: module java.base does not open java.lang to unnamed module @e91af20; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=ALL-UNNAMED at org.jboss.threads.JDKSpecific$ThreadAccess.<clinit>(JDKSpecific.java:32) at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:13) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1447) Exception in thread "executor-thread-1" java.lang.NoClassDefFoundError: Could not initialize class org.jboss.threads.JDKSpecific$ThreadAccess at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:13) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1447) Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalAccessError: module java.base does not open java.lang to unnamed module @e91af20; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=ALL-UNNAMED [in thread "vert.x-internal-blocking-1"] at org.jboss.threads.JDKSpecific$ThreadAccess.<clinit>(JDKSpecific.java:32) ... 3 more 2025-10-17 17:13:29,597 INFO [io.quarkus] (main) kubernetes-quickstart stopped in 0.085s [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- jar:3.4.1:jar (default-jar) @ kubernetes-quickstart --- [INFO] Building jar: /mnt/mysharedfolder/kubernetes-quickstart/target/kubernetes-quickstart-1.0.0-SNAPSHOT.jar [INFO] [INFO] --- quarkus:3.25.2:build (default) @ kubernetes-quickstart --- [INFO] [io.quarkus.kubernetes.deployment.PropertyUtil] Kubernetes manifests are generated with 'The container port http' having default value '8080'. The app and manifests will get out of sync if the property 'quarkus.http.port' is changed at runtime. [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Starting (local) container image build for jar using jib. [WARNING] [io.quarkus.container.image.jib.deployment.JibProcessor] Base image 'registry.access.redhat.com/ubi9/openjdk-21-runtime:1.21' does not use a specific image digest - build may not be reproducible [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Using base image with digest: sha256:360822c35c5741f542ab78fe123e6c4d9b68e0113a88d6e0250bb1f377b17f29 [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Container entrypoint set to [/opt/jboss/container/java/run/run-java.sh] [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Pushed container image localhost:8443/quarkus/kubernetes-quickstart:1.0.0-SNAPSHOT (sha256:8406a011b49a42ffb3f4c73c694644afb56c10e740c3c65a83fa9fbb0c8350a5) [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 17046ms [INFO] [INFO] --- failsafe:3.5.3:integration-test (default) @ kubernetes-quickstart --- [INFO] Tests are skipped. [INFO] [INFO] --- quarkus:3.25.2:native-image-agent (default) @ kubernetes-quickstart --- [INFO] Missing native-image-agent-base-config directory with native image agent configuration to transform [INFO] [INFO] --- failsafe:3.5.3:verify (default) @ kubernetes-quickstart --- [INFO] Tests are skipped. [INFO] [INFO] --- install:3.1.2:install (default-install) @ kubernetes-quickstart --- [INFO] Installing /mnt/mysharedfolder/kubernetes-quickstart/pom.xml to /home/vagrant/.m2/repository/org/acme/kubernetes-quickstart/1.0.0-SNAPSHOT/kubernetes-quickstart-1.0.0-SNAPSHOT.pom [INFO] Installing /mnt/mysharedfolder/kubernetes-quickstart/target/kubernetes-quickstart-1.0.0-SNAPSHOT.jar to /home/vagrant/.m2/repository/org/acme/kubernetes-quickstart/1.0.0-SNAPSHOT/kubernetes-quickstart-1.0.0-SNAPSHOT.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 40.013 s [INFO] Finished at: 2025-10-17T17:13:47Z [INFO] ------------------------------------------------------------------------
Removing the created Kubernetes objects
So, as I mentioned in my previous article, the Quarkus generated manifest kubernetes.yml, was applied to the Kubernetes cluster and created the following Kubernetes objects (in my custom nl-amis-development namespace):
- Service
- Deployment
- Replica Set
- Pod
Because I wanted to create the Kubernetes objects again, first I had to delete the existing ones. I repeated some of the steps I already described in a previous article.
[Quarkus – Kubernetes extension (reinvestigated, part 4), implementing a Service of service type NodePort]
In order to delete the Replica Set and all of the dependent Pods (in the nl-amis-development namespace), I used the following command on the Linux Command Prompt:
kubectl delete -n nl-amis-development replicaset $(kubectl get replicasets -n nl-amis-development -o=jsonpath='{range .items..metadata}{.name}{"\n"}{end}' | grep kubernetes-quickstart- | awk '{print $1}')
With the following output:
replicaset.apps "kubernetes-quickstart-6d8f86d78f" deleted
To delete a ReplicaSet and all of its Pods, use kubectl delete. The Garbage collector automatically deletes all of the dependent Pods by default.
[https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/#deleting-a-replicaset-and-its-pods]
In order to delete the Deployment, I used the following command on the Linux Command Prompt:
kubectl delete -n nl-amis-development deployment kubernetes-quickstart
With the following output:
deployment.apps "kubernetes-quickstart" deleted
In order to delete the Service, I used the following command on the Linux Command Prompt:
kubectl delete -n nl-amis-development service kubernetes-quickstart
With the following output:
service "kubernetes-quickstart" deleted
Recreating the Kubernetes objects in the nl-amis-development namespace, including a service with service type NodePort
Next, in order to recreate the Kubernetes manifests (in the nl-amis-development namespace), I used the following commands on the Linux Command Prompt:
cd /mnt/mysharedfolder/kubernetes-quickstart kubectl apply -f target/kubernetes/kubernetes.yml
With the following output:
service/kubernetes-quickstart created deployment.apps/kubernetes-quickstart created
Remark:
Be aware that I already created the nl-amis-development namespace object.
Then, I quickly checked whether the Pods were running successfully, via a series of commands on the Linux Command Prompt.
kubectl get services --all-namespaces
With the following output:
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 65d
kube-system kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 65d
kube-system metrics-server ClusterIP 10.43.45.122 <none> 443/TCP 65d
kube-system traefik LoadBalancer 10.43.141.14 10.0.2.15 80:30268/TCP,443:32611/TCP 65d
kubernetes-dashboard dashboard-metrics-scraper ClusterIP 10.43.18.175 <none> 8000/TCP 65d
kubernetes-dashboard kubernetes-dashboard ClusterIP 10.43.77.188 <none> 443/TCP 65d
nl-amis-development kubernetes-quickstart NodePort 10.43.208.251 <none> 8180:30010/TCP,5005:31906/TCP 9s
kubectl get replicasets --all-namespaces
With the following output:
NAMESPACE NAME DESIRED CURRENT READY AGE
kube-system coredns-5688667fd4 1 1 1 65d
kube-system local-path-provisioner-774c6665dc 1 1 1 65d
kube-system metrics-server-6f4c6675d5 1 1 1 65d
kube-system traefik-c98fdf6fb 1 1 1 65d
kubernetes-dashboard dashboard-metrics-scraper-749c668b7f 1 1 1 65d
kubernetes-dashboard kubernetes-dashboard-76b75d676c 1 1 1 65d
nl-amis-development kubernetes-quickstart-dcd557cfd 3 3 3 32s
kubectl get daemonset --all-namespaces
With the following output:
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-system svclb-traefik-dee09411 1 1 1 1 1 <none> 65d
kubectl get pods --all-namespaces
With the following output:
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5688667fd4-2hqfj 1/1 Running 14 (8h ago) 65d
kube-system helm-install-traefik-crd-kp2hl 0/1 Completed 0 65d
kube-system helm-install-traefik-vl7x8 0/1 Completed 2 65d
kube-system local-path-provisioner-774c6665dc-2b9pq 1/1 Running 14 (8h ago) 65d
kube-system metrics-server-6f4c6675d5-lljlv 1/1 Running 14 (8h ago) 65d
kube-system svclb-traefik-dee09411-c7lzh 2/2 Running 28 (8h ago) 65d
kube-system traefik-c98fdf6fb-stkt7 1/1 Running 14 (8h ago) 65d
kubernetes-dashboard dashboard-metrics-scraper-749c668b7f-s7rjt 1/1 Running 14 (8h ago) 65d
kubernetes-dashboard kubernetes-dashboard-76b75d676c-tq8fk 1/1 Running 14 (25m ago) 65d
nl-amis-development kubernetes-quickstart-dcd557cfd-9bmd6 1/1 Running 0 72s
nl-amis-development kubernetes-quickstart-dcd557cfd-f5vhj 1/1 Running 0 72s
nl-amis-development kubernetes-quickstart-dcd557cfd-zrwbt 1/1 Running 0 72s
kubectl get endpoints --all-namespaces
With the following output:
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice NAMESPACE NAME ENDPOINTS AGE default kubernetes 10.0.2.15:6443 65d kube-system kube-dns 10.42.0.162:53,10.42.0.162:53,10.42.0.162:9153 65d kube-system metrics-server 10.42.0.165:10250 65d kube-system traefik 10.42.0.158:8000,10.42.0.158:8443 65d kubernetes-dashboard dashboard-metrics-scraper 10.42.0.167:8000 65d kubernetes-dashboard kubernetes-dashboard 10.42.0.160:8443 65d nl-amis-development kubernetes-quickstart 10.42.0.178:5005,10.42.0.179:5005,10.42.0.180:5005 + 3 more... 109s
kubectl get endpoints -n nl-amis-development --output json
With the following output:
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice { "apiVersion": "v1", "items": [ { "apiVersion": "v1", "kind": "Endpoints", "metadata": { "annotations": { "endpoints.kubernetes.io/last-change-trigger-time": "2025-10-17T17:16:39Z" }, "creationTimestamp": "2025-10-17T17:16:37Z", "labels": { "app.kubernetes.io/managed-by": "quarkus", "app.kubernetes.io/name": "kubernetes-quickstart", "app.kubernetes.io/version": "1.0.0-SNAPSHOT", "endpoints.kubernetes.io/managed-by": "endpoint-controller" }, "name": "kubernetes-quickstart", "namespace": "nl-amis-development", "resourceVersion": "23654", "uid": "4893e38f-5e3d-4268-bc30-3a7461cff845" }, "subsets": [ { "addresses": [ { "ip": "10.42.0.178", "nodeName": "ubuntu2204.localdomain", "targetRef": { "kind": "Pod", "name": "kubernetes-quickstart-dcd557cfd-f5vhj", "namespace": "nl-amis-development", "uid": "e1a02732-a8c8-4916-802f-c515ba18fed4" } }, { "ip": "10.42.0.179", "nodeName": "ubuntu2204.localdomain", "targetRef": { "kind": "Pod", "name": "kubernetes-quickstart-dcd557cfd-zrwbt", "namespace": "nl-amis-development", "uid": "9b8faaea-0687-4f56-b19a-c2db21e8d1b2" } }, { "ip": "10.42.0.180", "nodeName": "ubuntu2204.localdomain", "targetRef": { "kind": "Pod", "name": "kubernetes-quickstart-dcd557cfd-9bmd6", "namespace": "nl-amis-development", "uid": "05ca798b-47f9-465f-8854-8add14cfbca5" } } ], "ports": [ { "name": "debug", "port": 5005, "protocol": "TCP" }, { "name": "ports", "port": 8080, "protocol": "TCP" } ] } ] } ], "kind": "List", "metadata": { "resourceVersion": "" } }
kubectl get nodes
With the following output:
NAME STATUS ROLES AGE VERSION
ubuntu2204.localdomain Ready control-plane,master 65d v1.33.3+k3s1
In order to determine the IP of the K3s node, I used the following commands on the Linux Command Prompt, as I described in a previous article:
[Creating a re-usable Vagrant Box from an existing VM with Ubuntu and k3s (with the Kubernetes Dashboard) and adding mysql, using Vagrant and Oracle VirtualBox]
nodeIP=$(kubectl get node ubuntu2204.localdomain -o yaml | grep address: | grep -E -o "([0-9]{1,3}[\.]){3}[0-9]{1,3}") echo "---$nodeIP---"
With the following output:
---10.0.2.15---
Below, you see an overview of my Kubernetes cluster at this moment:
From the Quarkus Kubernetes Extension documentation, I had a further look at the “Remote Debugging" paragraph.
[https://quarkus.io/guides/deploying-to-kubernetes]
Remote debugging
After your application has been deployed with the debug enabled, next you need to tunnel the traffic from your local host machine to the specified port of the java agent:
kubectl port-forward svc/<application name> 5005:5005
Using this command, you’ll forward the traffic from the "localhost:5005" to the kubernetes service running the java agent using the port "5005" which is the one that the java agent uses by default for remote debugging. You can also configure another java agent port using the property quarkus.kubernetes.remote-debug.address-port.
Finally, all you need to do is to configure your favorite IDE to attach the java agent process that is forwarded to localhost:5005 and start to debug your application. For example, in IntelliJ IDEA, you can follow this tutorial to debug remote applications.
[https://quarkus.io/guides/deploying-to-kubernetes#remote-debugging]
In order to forward the traffic, I used the following command on the Linux Command Prompt:
kubectl port-forward -n nl-amis-development service/kubernetes-quickstart 5005:5005
With the following output:
Forwarding from 127.0.0.1:5005 -> 5005
In IntelliJ IDEA on my Windows laptop, from the GreetingResource class, via the menu I navigated to Run | Debug… (Alt+Shift+F9) | localhost5005:
So, now I got the following error:
Unable to open debugger port (localhost:5005): java.net.SocketException "Connection reset"
In order to forward the traffic (on all addresses) , I used the following command on the Linux Command Prompt:
kubectl port-forward -n nl-amis-development --address 0.0.0.0 service/kubernetes-quickstart 5005:5005
With the following output:
Forwarding from 0.0.0.0:5005 -> 5005
By the way, below you see another port forward example:
# Listen on port 8888 on all addresses, forwarding to 5000 in the pod kubectl port-forward --address 0.0.0.0 pod/mypod 8888:5000
[https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/]
Remark about the option –address:
--address strings Default: "localhost"
Addresses to listen on (comma separated). Only accepts IP addresses or localhost as a value. When localhost is supplied, kubectl will try to bind on both 127.0.0.1 and ::1 and will fail if neither of these addresses are available to bind.
[https://kubernetes.io/docs/reference/kubectl/generated/kubectl_port-forward/]
Again, in IntelliJ IDEA on my Windows laptop, from the GreetingResource class, via the menu I navigated to Run | Debug… (Alt+Shift+F9) | localhost5005:
The following output was visible in the debugger console:
Connected to the target VM, address: 'localhost:5005', transport: 'socket'
And the following output was visible on the Linux Command Prompt:
Handling connection for 5005
In order to forward local port 8090 to port 30100 on the K3s node ($nodeIP), I used the following command on the Linux Command Prompt, as I described in a previous article:
[Creating a re-usable Vagrant Box from an existing VM with Ubuntu and k3s (with the Kubernetes Dashboard) and adding mysql, using Vagrant and Oracle VirtualBox]
socat tcp-listen:8090,fork tcp:10.0.2.15:30010 &
With the following output:
Then, in the Web Browser on my Windows laptop, I entered the URL:
This triggered the debugger:
As you can see the program was at the breakpoint on line 14. Next, I chose “Resume Program”.
And, then I got the following expected result:
Next, in IntelliJ IDEA, I in the tab “localhost5005”, I clicked on “ Stop localhost5005’ ”.
With the following output in the debugger console:
Disconnected from the target VM, address: 'localhost:5005', transport: 'socket'
Next, I closed the tab “localhost5005”.
So, remote JVM debugging a Java application from a Kubernetes cluster running in my Linux demo environment also worked 😊.
I conclude this article.
In this article, you can read more about the steps I took to remotely debug a Java application that is running in a container on a Kubernetes cluster in my already existing Linux demo environment, including Quarkus and K3s (a lightweight certified Kubernetes distribution) and setup via Vagrant.
I went about this, step by step via:
- Remote JVM debugging a Java application directly on my Windows laptop
- Remote JVM debugging a Java application from my Linux demo environment
- Remote JVM debugging a Java application from a Kubernetes cluster running in my Linux demo environment
In a next article, you can read more about using development mode remotely, so that you can run Quarkus in a container environment and have changes made to your local files become immediately visible.
Feel free, among my other articles on this subject, to read:
- “Quarkus – Supersonic Subatomic Java, trying out Quarkus guide “Quarkus - Kubernetes extension” (part 1)”, September 27, 2020
In this article, you can read more about the steps I took, trying out the Quarkus code guide “Quarkus - Kubernetes extension”, and more specific the steps related to using custom labels and annotations. - “Quarkus – Supersonic Subatomic Java, trying out some Quarkus code guides (part2)”, October 3, 2020
In this article, you can read more about the steps I took, trying out the Quarkus code guide “Quarkus - Kubernetes extension”, and more specific the steps related to customizing the namespace, the service account name, the number of replicas and the type of service. - “Quarkus – Supersonic Subatomic Java, trying out Quarkus guide “Quarkus - Kubernetes extension (part 3)”, October 10, 2020
In this article, you can read more about the steps I took, trying out the Quarkus code guide “Quarkus - Kubernetes extension”, and more specific the steps related the automatic deployment of the generated resources to a target platform, in my case K3s (lightweight certified Kubernetes distribution).