Production Readiness & Deployment

Overview

What changes when your Spring Boot app moves from your laptop to a production cluster: fat JAR, layered JAR for Docker caching, graceful shutdown, Kubernetes probes wired to Actuator, and JVM memory in containers.

Official: Container Images, Graceful Shutdown.


Table of contents

  1. Fat JAR
  2. Layered JAR for Docker
  3. Graceful shutdown
  4. Kubernetes probes
  5. JVM memory in containers
  6. Interview questions
  7. Summary

1. Fat JAR

mvn package produces one JAR that contains your app plus all dependencies. Run it with:

java -jar app.jar

The embedded Tomcat starts inside. No external servlet container needed.


2. Layered JAR for Docker

Boot can split the fat JAR into layers so Docker caches the slow parts:

Layer Changes
dependencies Rarely — only when libraries change
spring-boot-loader Almost never
snapshot-dependencies Occasionally
application Every code change

Dockerfile copies dependencies first, app code last → rebuilds are fast and the resulting images push less data:

FROM eclipse-temurin:21-jre AS builder
WORKDIR /app
COPY app.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

3. Graceful shutdown

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Behaviour on SIGTERM:

  1. Stop accepting new requests.
  2. Wait up to 30s for in-flight requests to finish.
  3. Exit.

Critical for Kubernetes rolling updates — without it, in-flight requests are killed when the pod terminates.


4. Kubernetes probes

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
Probe Question Failure action
Liveness Is the process alive? Restart the container
Readiness Should I receive traffic? Remove from Service endpoints (no restart)

Rule: keep liveness cheap. Put DB / message broker checks in readiness so a failing dependency drains traffic instead of crash-looping the container.


5. JVM memory in containers

The JVM doesn’t read your container CPU/memory limit unless you tell it:

-XX:MaxRAMPercentage=75.0

That gives the JVM 75 % of the container’s memory limit for the heap. Without it, the JVM may use the host’s RAM total and get OOM-killed.

Also useful in production:

  • GC logging (-Xlog:gc*:file=/var/log/gc.log) or JFR for triage.
  • Heap dumps on OOM (-XX:+HeapDumpOnOutOfMemoryError).

6. Interview questions

Q: Why use a layered Docker build? A: Dependencies change less often than app code. Putting them in a separate layer means rebuilding only changes the small application layer, making builds and image pushes much faster.

Q: What is graceful shutdown? A: On SIGTERM, stop accepting new requests, let in-flight ones finish within a timeout, then exit. Essential for K8s rolling updates.

Q: Should the liveness probe check the database? A: No. Liveness should be cheap. Put DB checks in readiness so a DB outage drains traffic from the pod without restarting it.

Q: How do you size the JVM heap in a container? A: -XX:MaxRAMPercentage=75 (or similar) so the JVM uses a percentage of the container’s memory limit instead of the host’s.


7. Summary

Topic Rule
Image Layered JAR for cache wins
Shutdown server.shutdown=graceful + timeout
Probes Cheap liveness, deep readiness
Heap MaxRAMPercentage so JVM respects container limits

Next steps


Happy Learning

Next: Proxies & Common Pitfalls