Run Spring Boot on Kubernetes

Khimananda Oli 7 min read Programming and Languages
Run Spring Boot on Kubernetes

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.

Build Stageeclipse-temurin:21-jdk./gradlew build-x testRuntime Stagegcr.io/distroless/java21COPY app.jar /app.jarUSER nonrootK8s Pod~85MB Image SizeNo Shell / No RootFast Cold Start
Optimized multi-stage build pipeline to run Spring Boot on Kubernetes with minimal runtime footprint

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/liveness with 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.

Container Memory Limit: 2GiHeap (-Xmx): 1536MiNon-Heap OverheadMetaspace + ThreadsSafety Buffer (~25%)GC + Direct BuffersOOMKilled Threshold
Memory budget breakdown to safely run Spring Boot on Kubernetes without OOMKilled failures

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 MethodUse CaseSecurity LevelUpdate Mechanism
Environment VariablesSimple flags, feature togglesLow (visible in inspect)Pod restart required
ConfigMap Volume Mountapplication.yml, logging configMediumAuto-reload with watch
Secret Volume MountDB passwords, TLS certsHigh (base64 encoded)Pod restart required
External Secrets OperatorVault/AWS SM integrationHighest (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.

Frequently Asked Questions

Use Eclipse Temurin JRE Alpine or Ubuntu Noble for production. These images minimize attack surface and size while maintaining glibc compatibility needed by native libraries. Avoid full JDK images in deployment manifests to reduce CVE exposure and improve pod startup latency significantly.

Expose /actuator/health/liveness and /actuator/health/readiness endpoints via spring-boot-starter-actuator. Map these to Kubernetes livenessProbe and readinessProbe HTTP checks on port 8080. Set initialDelaySeconds to match your application startup time to prevent premature restarts during initialization phases.

Yes, enable server.shutdown=graceful in application properties. Configure spring.lifecycle.timeout-per-shutdown-phase to allow in-flight requests to complete before SIGTERM terminates the container. Align this timeout with your Kubernetes terminationGracePeriodSeconds setting to avoid forced kills during rolling updates.

Set container memory limits 20 percent above JVM heap plus metaspace overhead. Use -XX:MaxRAMPercentage=75 instead of fixed -Xmx values so the JVM respects cgroup limits dynamically. This prevents OOMKilled events while avoiding resource waste in variable-load Kubernetes environments.

Mount ConfigMaps as files under /config or use Spring Cloud Kubernetes to reload properties automatically. Store secrets in Kubernetes Secrets mounted as volumes, never environment variables. Enable spring.config.import=kubernetes: to merge cluster-native configuration with embedded application.yml safely.

Native images reduce startup from seconds to milliseconds and cut memory usage by half. Build complexity increases due to reflection configuration requirements. Use native compilation for high-density autoscaling workloads where cold start latency matters more than build pipeline simplicity.

Use connection poolers like PgBouncer or RDS Proxy between pods and databases. Configure HikariCP maximum-pool-size based on pod count times replicas to avoid exhausting database connections during scale-up events. Implement retry logic with exponential backoff for transient connection failures.

Istio or Linkerd integrate well via sidecar proxies without code changes. Use Spring Cloud Gateway for application-layer routing when you need protocol-aware load balancing. Service meshes add latency overhead, so benchmark your specific traffic patterns before adopting in production clusters.

Check kubectl logs with --previous flag to see crash output. Verify JVM flags respect container memory limits and that health endpoints respond within probe timeouts. Inspect events with kubectl describe pod to identify OOMKilled, image pull errors, or failed volume mounts causing restart loops.

No, Kubernetes requires container runtime interfaces. You can use Buildpacks via kpack or Skaffold to create OCI-compliant images without writing Dockerfiles. These tools detect Spring Boot automatically and produce optimized layered images suitable for cluster deployment without manual containerization steps.

Restrict actuator access using network policies limiting ingress to monitoring namespaces only. Disable sensitive endpoints like env and heapdump unless required. Authenticate management endpoints separately from business APIs using distinct security filter chains to prevent credential leakage through health check paths.

Output structured JSON logs to stdout using Logback JSON encoder. Let Fluent Bit or Vector collect logs via DaemonSet rather than embedding agents in application containers. Include trace IDs and pod metadata in log context for correlation across distributed traces and cluster events.

Use jarmode=layertools to extract dependencies, resources, and classes into separate image layers. Order Dockerfile COPY commands from least to most frequently changing content. This maximizes registry cache hits during deployments and reduces pull times across nodes sharing common dependency layers.

Helm suits teams managing multiple environments with parameterized templates and chart dependencies. Kustomize works better for overlay-based configurations without templating complexity. Both integrate with GitOps workflows via ArgoCD or Flux. Choose based on team familiarity and configuration drift tolerance levels.

Expose Micrometer Prometheus endpoint at /actuator/prometheus. Configure ServiceMonitor for automatic discovery by Prometheus Operator. Track key metrics including jvm_memory_used_bytes, http_server_requests_seconds_count, and hikaricp_connections_active to correlate application performance with infrastructure resource utilization patterns.