What is a multi-stage Docker build and why is it useful for Java apps?
Fast recall
Quick revision
Core answer
FROM stage and copies selected artifacts from an earlier stage into the final stage. A Java build stage can compile the JAR with a JDK/build tool, while the final stage contains only the runtime and artifact. This separates build dependencies from runtime and can reduce image size and attack surface; the actual size depends on chosen images and artifact.Interview-ready guide
Interview answer
A multi-stage Docker build uses more than one FROM stage. One stage can contain the JDK and build tool, while the final stage copies only the tested runtime artifact and uses the runtime needed by the application.
For example, a stage based on a JDK image can run ./mvnw test package. A later JRE stage uses COPY --from=build /workspace/target/app.jar /app/app.jar and starts it with Java. Maven caches, source, and compiler tools from the first stage are not copied unless I explicitly copy them.
This separates build and runtime concerns and often reduces final image size and attack surface. The result is not automatically secure or a fixed 600-to-200 MB improvement: base-image choice, native libraries, JVM needs, ownership, updates, and vulnerability scanning still matter. I also pin trusted base versions and run as a non-root user where possible.
What is a multi-stage Docker build and why is it useful for Java apps
Direct Answer
A multi-stage Dockerfile has more than one FROM stage and copies selected artifacts from an earlier stage into the final stage. A Java build stage can compile the JAR with a JDK/build tool, while the final stage contains only the runtime and artifact. This separates build dependencies from runtime and can reduce image size and attack surface; the actual size depends on chosen images and artifact.
Code Example
# Stage 1: Build stage (large, includes Maven and JDK)
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /build
# Copy source and build dependencies
COPY pom.xml .
COPY src/ src/
# Compile the application using Maven
RUN ./mvnw clean package -DskipTests
# Result: /build/target/order-service.jar (20MB)
# Stage 2: Runtime stage (small, JRE only)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Copy only the compiled JAR from the builder stage
COPY --from=builder /build/target/order-service.jar app.jar
# Expose port and run
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# Final image contains:
# - Alpine Linux (5MB)
# - JRE 17 (170MB)
# - Application JAR (20MB)
# Total: ~195MB
# What's excluded: Maven (80MB), JDK compiler (200MB), source code (10MB)
# Single-stage image would be ~500MB
Common Mistakes & Pitfalls
- Including build tools in final image: Freshers often use a single FROM with the full JDK and leave Maven in the final image because it was needed for the build. They don't realize that build tools can be discarded—they're only needed at build time, not runtime. This bloats the image unnecessarily.
- Copying entire directories: Using COPY . . in the runtime stage instead of COPY --from=builder target/app.jar. This brings source code, test files, and other build artifacts into the final image. Always explicitly copy only the compiled artifact.
- Not using alpine or slim variants: Using eclipse-temurin:17 (650MB) instead of eclipse-temurin:17-jre-alpine (180MB) for the runtime stage. Alpine is a minimal Linux distribution that cuts hundreds of megabytes with no functional downside for most apps.
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.