Java on Kubernetes is not “just Docker”
A JVM container is sensitive to CPU throttling and memory limits. On Java 17/21, container support is on by default, but heap sizing still needs intent. Prefer -XX:MaxRAMPercentage=75 over a hardcoded -Xmx so Pod limits and the JVM stay aligned.
Probes: startup, readiness, liveness
Spring Boot cold start is normal. Without a startupProbe, kubelet may kill the Pod before the app is up (classic CrashLoopBackOff). Readiness gates traffic until dependencies are ready. Keep liveness conservative: restart only when the process is stuck, not when the database blips.
startupProbe:
httpGet: { path: /actuator/health/liveness, port: 8081 }
failureThreshold: 30
periodSeconds: 10
Expose Actuator on a separate management port so business traffic and ops endpoints stay separated.
Resources: memory request = limit
Memory is not compressible. For Java, teams usually set requests.memory == limits.memory to avoid surprise OOMKills. Be careful with CPU limits — they often throttle GC threads. Many production setups use a CPU request without a hard limit, or reserve at least 1–2 cores per service.
Graceful shutdown
server.shutdown=gracefulin Spring Boot;- a short
preStopsleep so the endpoint can leave the Service; - enough
terminationGracePeriodSeconds.
Config and secrets
ConfigMaps for non-secret settings; Secrets or an external vault for credentials. Never bake secrets into the image. Spring Cloud Kubernetes Config or envFrom + a prod profile ConfigMap are common patterns.
Images
Multi-stage builds, slim/distroless JRE, and Spring Boot layertools for CI layer caching are industry defaults. Run as non-root to satisfy typical cluster policies.
Takeaway
Successful Java on Kubernetes is mostly about aligned memory/CPU, correct probes, graceful shutdown, and observability — the same themes you see in production write-ups from platform engineers and Spring/K8s reference architectures.
