Kubernetes for Java Developers: Getting Started
Back to Blog

Kubernetes for Java Developers: Getting Started

6 min read
Read in Deutsch

Why Container Orchestration?

Docker has revolutionized the way we deliver software in recent years. Containers are lightweight, portable, and reproducible. But anyone who has tried running a handful of Docker containers in production knows the challenges: How do I distribute containers across multiple hosts? What happens when a container crashes? How do I scale under load?

This is exactly where container orchestration comes into play. While Docker Compose and Docker Swarm are sufficient for simple scenarios, practice quickly shows that a more powerful tool is needed. Kubernetes -- originally developed by Google and now an open-source project under the Cloud Native Computing Foundation -- has established itself as the leading solution.

For Java teams that already package their Spring Boot applications in Docker containers, Kubernetes is the logical next step. In this article, we look at the core concepts and take the first practical steps.

The Core Concepts of Kubernetes

Kubernetes comes with its own terminology that can seem somewhat overwhelming at first. At their core, however, the concepts are quite intuitive once you understand them.

Pods

A Pod is the smallest deployable unit in Kubernetes. It contains one or more containers that share networking and storage. In most cases, exactly one container runs per Pod -- for example, a Spring Boot application.

Pods are ephemeral by nature. Kubernetes can terminate and restart them at any time. This may sound concerning at first, but it is a central design principle: applications should be stateless and replaceable at any time.

Deployments

A Deployment describes the desired state of an application: Which container image should run? How many instances (replicas) should there be? Kubernetes then automatically ensures that this state is maintained. If a Pod crashes, it gets restarted. If a new image is deployed, a rolling update occurs without downtime.

Services

When Pods are ephemeral and their IP addresses can constantly change, you need a stable address at which an application is reachable. That is exactly what a Service provides. It offers a fixed DNS name and a fixed IP, distributing incoming requests via load balancing across the underlying Pods.

ConfigMaps and Secrets

Configuration values and credentials should not be hardcoded into the container image. Kubernetes provides ConfigMaps (for non-sensitive configuration) and Secrets (for passwords, tokens, and the like) for this purpose. Both can be mounted as environment variables or as files into the Pod -- ideal for Spring Boot's externalized configuration via application.properties or environment variables.

How Java Applications Fit into Kubernetes

Spring Boot comes with many built-in features that make running in Kubernetes easier:

  • Embedded Server: Spring Boot ships with Tomcat included. The container image does not need an external application server.
  • Health Endpoints: With Spring Boot Actuator, /health endpoints are available that Kubernetes can use as liveness and readiness probes.
  • Externalized Configuration: Profiles and environment variables make it easy to run the same application in different environments.
  • Fat JARs: A single JAR contains everything the application needs. This keeps the Dockerfile pleasantly lean.

A typical Dockerfile for a Spring Boot application looks like this:

FROM openjdk:8-jre-alpine
COPY target/my-app-1.0.0.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

Kubernetes Configuration in Practice

Kubernetes resources are described in YAML files. Here is a complete example of a Deployment and a Service for a Spring Boot application:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: my-spring-app
  labels:
    app: my-spring-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-spring-app
  template:
    metadata:
      labels:
        app: my-spring-app
    spec:
      containers:
        - name: my-spring-app
          image: registry.example.com/my-spring-app:1.0.0
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 5
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "production"
---
apiVersion: v1
kind: Service
metadata:
  name: my-spring-app
spec:
  type: ClusterIP
  selector:
    app: my-spring-app
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP

Some important details in this configuration:

  • Replicas: Two instances provide basic high availability.
  • Liveness Probe: Kubernetes regularly checks whether the application is still responding. The initialDelaySeconds value gives the JVM enough time to start up.
  • Readiness Probe: Only when this check succeeds does the Service route traffic to the Pod.
  • Resource Limits: Especially important for Java applications, since the JVM tends to consume memory. The limits protect the cluster from a single Pod consuming all resources.

First Steps with Minikube

For getting started locally, Minikube is the tool of choice. It creates a virtual machine with a single-node Kubernetes cluster on your own machine.

The setup is straightforward:

# Start Minikube
minikube start

# Check cluster status
kubectl cluster-info

# Create deployment
kubectl apply -f deployment.yaml

# Show pods
kubectl get pods

# View logs of a pod
kubectl logs my-spring-app-abc123

# Open service in browser
minikube service my-spring-app

With kubectl, you have a powerful command-line tool at your disposal that enables all interactions with the cluster. The command kubectl get pods -w is particularly useful -- it shows in real time how Pods are created, started, and transition to the Ready state.

Things to Watch Out For

Getting started with Kubernetes is easier than often assumed, but there are some points that Java developers should keep in mind:

  • JVM Memory and Container Limits: The JVM does not automatically detect container memory limits correctly. It is recommended to set -Xmx explicitly and leave room for the JVM's off-heap memory.
  • Startup Time: Java applications take longer to start than, say, Go or Node.js services. The initialDelaySeconds for probes should be configured generously accordingly.
  • Logging: Kubernetes collects everything written to stdout/stderr. Spring Boot writes to the console by default -- which already fits perfectly.
  • Graceful Shutdown: During a rolling update, the application should finish processing in-flight requests before shutting down. Spring Boot supports this, but it must be configured correctly.

Conclusion

Kubernetes may seem complex at first glance, but the core concepts are clearly structured and well accessible for Java developers. If you are already using Docker, you have already overcome the hardest hurdle. Kubernetes builds on that and solves the problems that inevitably arise in production: scaling, fault tolerance, and zero-downtime deployments.

Our advice: install Minikube, take an existing Spring Boot application, and deploy it to the local cluster. Experiment with scaling (kubectl scale deployment my-spring-app --replicas=5) and simulate failures (simply delete a Pod and watch how Kubernetes replaces it). Hands-on experience teaches more than any documentation.

In upcoming articles, we will dive deeper into advanced topics: Ingress configuration, persistent storage, Helm Charts, and CI/CD pipelines with Kubernetes.

Patrick Hütter

Written by

Patrick Hütter

Founder & Software Architect

Software architect, engineer and entrepreneur. Patrick has been building products and platforms for over a decade — from enterprise backends and cloud-native infrastructure to AI-powered applications. As founder of encircle360, he combines deep technical expertise with entrepreneurial vision, driving open source projects that create real impact.