
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Deploying Java applications to containers requires specific tuning that generic tutorials often miss. To successfully run Spring Boot on Kubernetes, you must align the JVM’s memory model with container limits, configure native health endpoints, and handle SIGTERM signals for zero-downtime rollouts. This guide provides the exact configuration patterns I use in production environments to prevent OOMKilled errors and failed readiness checks.
How do you optimize Spring Boot container images for Kubernetes?
The foundation of a stable deployment is the container image itself. When you reduce Docker image size with multi-stage builds, you decrease attack surface and improve pull latency across cluster nodes. For Spring Boot 3.x, avoid running as root and never include a full JDK in the final runtime layer.
Use Eclipse Temurin or Amazon Corretto as your base. The key is separating compilation from execution. Here is a production-grade Dockerfile that works consistently across EKS, GKE, and AKS:
# Build stage
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /workspace
COPY . .
RUN ./gradlew bootJar --no-daemon -x test
# Runtime stage
FROM gcr.io/distroless/java21-debian12
WORKDIR /app
COPY --from=builder /workspace/build/libs/*.jar app.jar
USER nonroot:nonroot
ENTRYPOINT ["java", "-jar", "app.jar"] This approach yields images under 90MB. Distroless images contain no package manager, shell, or unnecessary utilities, which satisfies most SOC 2 and ISO 27001 compliance requirements for minimal attack surface. Always specify the exact tag rather than latest to ensure reproducible deployments when you manage multiple environments in IaC.
How do you configure health probes for Spring Boot on Kubernetes?
Kubernetes relies on three distinct probe types to manage pod lifecycle. A common mistake is pointing all probes at a single /health endpoint. Spring Boot Actuator exposes separate endpoints specifically designed for Kubernetes orchestration. You should reference our guide on the four golden signals of monitoring to understand why distinguishing availability from readiness matters for SLO tracking.
- Liveness Probe: Determines if the application is stuck and needs a restart. Use
/actuator/health/liveness. Never include database checks here; a slow DB should not trigger pod restarts. - Readiness Probe: Determines if the pod can accept traffic. Use
/actuator/health/readiness. Include downstream dependency checks here. - Startup Probe: Protects slow-starting Java apps from being killed before initialization completes. Use
/actuator/health/livenesswith generous failure thresholds.
Add this to your application.yml to expose the correct endpoints without additional dependencies:
management:
endpoint:
health:
probes:
enabled: true
group:
liveness:
include: livenessState
readiness:
include: readinessState, db, redis
endpoints:
web:
exposure:
include: health,info,prometheus In your Kubernetes manifest, map these endpoints explicitly. Set initialDelaySeconds to 0 when using startup probes, as the startup probe handles the initial warm-up period. This prevents race conditions where liveness checks fail before the JVM finishes loading classes.
How do you prevent OOMKilled errors when running Spring Boot on Kubernetes?
Java memory management inside containers remains the most frequent cause of instability. The JVM allocates heap plus non-heap memory (metaspace, thread stacks, direct buffers, GC overhead). If you set container memory limit equal to max heap size, the pod will be OOMKilled. When you configure Kubernetes resource limits and requests, always account for at least 25–30% overhead beyond -Xmx.
For a 2Gi container limit, configure the JVM like this:
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=75.0"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "2Gi"
cpu: "2000m" Using MaxRAMPercentage instead of fixed -Xmx values makes your configuration portable across different instance sizes. The UseContainerSupport flag is enabled by default in Java 10+ but verify it in your base image. Always set memory requests equal to limits for Java workloads to guarantee QoS class and prevent eviction during node pressure. CPU requests can be lower than limits to allow burst capacity during garbage collection pauses.
How do you handle graceful shutdown for Spring Boot on Kubernetes?
Kubernetes sends SIGTERM when terminating pods. Without proper handling, in-flight HTTP requests drop and users see 502 errors. Spring Boot 3.x supports graceful shutdown natively, but you must coordinate it with Kubernetes termination grace periods. This coordination is essential when implementing blue-green and canary deploys on Kubernetes to maintain zero-downtime releases.
Enable graceful shutdown in your application configuration:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s Then add a preStop hook in your deployment spec. This gives the kube-proxy time to update iptables rules before your app stops accepting new connections:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 60 The math matters: preStop sleep (5s) + shutdown timeout (30s) + buffer (5s) = 40s. Set terminationGracePeriodSeconds higher than this sum. If your app takes longer to drain connections, increase both the Spring timeout and the grace period proportionally. Test this with load testing tools like k6 during staging validation to confirm zero dropped requests during scale-down events.
What are the best practices for externalized configuration and secrets?
Never bake environment-specific configuration into container images. Use ConfigMaps for non-sensitive settings and Secrets for credentials. Mount them as files rather than environment variables to avoid leaking sensitive data in process listings or crash dumps. For database passwords and API keys, integrate with HashiCorp Vault or AWS Secrets Manager using the Secrets Store CSI Driver.
| Configuration Method | Use Case | Security Level | Update Mechanism |
|---|---|---|---|
| Environment Variables | Simple flags, feature toggles | Low (visible in inspect) | Pod restart required |
| ConfigMap Volume Mount | application.yml, logging config | Medium | Auto-reload with watch |
| Secret Volume Mount | DB passwords, TLS certs | High (base64 encoded) | Pod restart required |
| External Secrets Operator | Vault/AWS SM integration | Highest (encrypted at rest) | Auto-sync without restart |
For Spring Cloud Kubernetes, add the dependency to auto-reload ConfigMaps without restarting pods. This reduces downtime during configuration changes but requires careful testing to ensure beans reinitialize correctly. Always validate configuration schemas in CI pipelines before deployment to catch typos that would otherwise cause CrashLoopBackOff states in production.
Run Spring Boot on Kubernetes with Confidence
Successfully operating Java microservices in production requires attention to JVM-container alignment, probe semantics, and termination handling. Apply these patterns systematically: optimize your image build, separate liveness from readiness, budget memory for non-heap overhead, and coordinate graceful shutdown windows. Monitor your deployments using Prometheus metrics monitoring fundamentals to validate that your resource allocations match actual usage patterns over time.
If your team needs help auditing existing Spring Boot deployments or designing a compliant Kubernetes platform, reach out through my contact page. I work with organizations across Nepal and globally to build infrastructure that passes audits and survives traffic spikes.