Docker and Java: Application Containerization — A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Docker and Java: Why Containerization Changes Development

Modern software development is unthinkable without containerization. Docker has become the de facto standard for packaging, delivering, and running applications. For Java developers, using Docker solves key problems: "it works on my machine" and the complexity of environment setup. In this article, we will break down how to containerize Java applications, from a simple JAR file to a complex microservice on Spring Boot.

Docker allows you to isolate an application along with its dependencies (JVM, libraries, system utilities) in a lightweight portable container. This is especially important for Java, where the JDK version, memory settings, and system libraries can greatly affect program behavior.

What you will learn from this article

  • Creating a Dockerfile for Java applications
  • Optimizing image sizes
  • Working with multi-module Maven/Gradle projects
  • Best practices for security and performance

1. Basics: Creating a Dockerfile for a Java Application

Let's start with a simple example. Suppose you have a Java application built into a JAR file. Here is a minimal Dockerfile that will package it into a container:

# Use the official OpenJDK image
FROM openjdk:17-jdk-slim

# Set the working directory
WORKDIR /app

# Copy the JAR file into the container
COPY target/myapp.jar app.jar

# Expose the port the application listens on
EXPOSE 8080

# Command to run
ENTRYPOINT ["java", "-jar", "app.jar"]

This Dockerfile does three simple things: takes a base image with JDK 17, copies the compiled JAR, and runs it. However, in real projects, this approach is inefficient — every time the code changes, Docker rebuilds the entire image, including copying the JAR.

Multi-stage build

To separate the build and execution stages, use multi-stage Dockerfiles. This reduces the final image by excluding build tools (Maven, Gradle, JDK for compilation).

# Build stage
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# Final stage
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

The first stage uses a full Maven image for compilation, the second uses a minimal JRE image for execution. The final image weighs ~200 MB instead of 700+ MB.

2. Image Optimization: Reducing Size and Speeding Up Builds

Large images slow down deployment and increase load times. Here are several techniques for Java:

Use JLink to create a minimal JRE

Starting with Java 9, you can create a custom JRE containing only the necessary modules. This radically reduces the image size.

FROM openjdk:17-jdk-slim AS jre-builder
RUN jlink \
    --add-modules java.base,java.logging,java.sql \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=2 \
    --output /jre

FROM debian:bookworm-slim
COPY --from=jre-builder /jre /jre
WORKDIR /app
COPY target/myapp.jar app.jar
ENV PATH="/jre/bin:${PATH}"
ENTRYPOINT ["java", "-jar", "app.jar"]

Such an image can weigh only 40-60 MB instead of 200+ MB.

Docker layer caching

Docker caches each layer. To speed up builds, place rarely changed files (dependencies) at the beginning of the Dockerfile, and frequently changed files (source code) at the end.

FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /build
# First, copy only pom.xml and download dependencies
COPY pom.xml .
RUN mvn dependency:go-offline
# Then copy sources and build
COPY src ./src
RUN mvn clean packag                

Blogs

Book Recommendations