At Devoxx Belgium 2025, I was able to talk about what happens after you build your container. In theory, it all sounds very simple. In practice, getting your application in a production environment is where multiple technical choices and complexity are waiting, requiring considerable time and resources.
How do you balance control for clarity? Which activity is best to keep visible and which one should be automated to streamline and simplify deployment activities? What tools are available to Java developers? Let’s dig in!
Abstractions & the Hidden Complexity of Java App Deployment
When thinking in abstract terms, deploying a modern enterprise Java application with Kubernetes (K8s) can seem like an intuitive, standardized process. It almost sound like a poem:
Hello.
I’m an endpoint.
A method in a class.
Sealed in a jar
next to my runtime;
Shipped in a container.
Inside a pod.
Within a namespace.
Deployment keeps me scheduled
on the nodes
of the wider cluster
exposed by a service.
Adjusted with a config map.
My secrets are a secret
and through ingress I reach out.
One controller guides my packets.
The second gives me name.
Yet another proves me valid.
There’s more of us,
but we’re not the same.
You hear me in network calls.
You see me in logs and metrics.
You know me through past
selves,
older versions,
remembered settings.
Layer on layer
object by object
abstraction becomes reality.
In conceptual terms, that’s exactly what happens. However, this abstraction leaves out all the details, which make the real-world process demanding. Every step is powered by YAML, the universal configuration language that defines how your container becomes a running service.
YAML: The Language of Structure
The least abstract solution you can use is certainly YAML, which tells Kubernetes everything, e.g. what image to pull, which ports to expose, how many replicas to create, where to route traffic and how to inject environment variables or secrets. Thus, it is extremely structured as well as verbose.
Also, if you are deploying a Java application, you are likely deploying multiple microservices and variations. These look similar in their structure, repeating 90% of the codebase, but differ in multiple lines, such as config map and ingress, with only minor changes.
In the example I presented at Devoxx Belgium, there were 14 different lines of code between two related Jakarta EE applications. Since these changes are not major code rewrites but small tweaks, abstracting away this task can be extremely beneficial to developers. The example is just a proxy to show how we are often dealing with multiple applications running on the same platform, sharing almost identical deployment structures. However, we’re typically missing a clean way to express that relationship. Standard Kubernetes tooling doesn’t give us the expressive power to say, “these two applications share the same deployment skeleton.”
Searching for Abstraction
This gap has led many teams to look for ways to express such similarities more elegantly, i.e. to find tools that can capture common structures without excessive repetition. In response, there are a number of tools today that can theoretically help by templating, automating and reducing duplication, so that Java developers can focus on what’s unique per deployment.
Examples include Kustomize, Helm, Jsonnet and YokeCD. However, it’s important to carefully evaluate the impact these can have on simplifying deployments. In fact, there are instances where the lines of code needed are more than those required if using YAML per se. At Devoxx Belgium, I showed how in one case, 23 lines were needed – 9 more than the 14 for YAML! In addition, the broader goal of enabling a higher-level abstraction that captures common deployment patterns natively remains something most standard tools still struggle with.
Beside these options, there are technologies that offer a higher abstraction. For example, Infrastructure as Code (IaC) solutions like Pulumi offer an abstraction that feels closer to software engineering and further from a wall of YAML.
However, the process with IaC can still feel a bit laborious, as the abstraction exists, yet expressing and maintaining it often requires significant manual work. This means that while we raised the abstraction level, we haven’t yet escaped the cycle of describing, deploying, debugging and redeploying.
Payara Qube: The Ultimate Abstraction Layer
With Payara Qube, the abstraction for Java gets even simpler, optimizing the developer experience. Think of it as an automated, fully managed, cloud-native runtime for deploying, running and scaling Java applications based on Jakarta EE, Spring or Quarkus frameworks. Instead of building and wiring all the pieces yourself, you hand over your enterprise Java application binaries and minimal configuration variables, i.e. JDK version, CPU reservation and config properties. From there, Payara Cube handles the rest.
It provides a preconfigured Kubernetes cluster and surrounding services, provisioning the Kubernetes resources, attaching monitoring, exposing metrics and logs as well as letting you take thread or heap dumps directly through its dashboard. You can deploy and manage multiple Java frameworks side by side, each isolated in its own namespace but observable through a unified view.
Ready to See Payara Qube in Action?
If you’re tired of maintaining fragile YAML files to get your app online, it’s time to try Payara Qube.
Upload your Jakarta EE, Spring Boot or Quarkus application and watch this abstraction run.