Packaging & Running Spring Boot Apps
Package a Spring Boot application into an executable JAR with mvn package, run it standalone, and containerize it with a simple Docker image.
Introduction
A Spring Boot application is not truly finished until it can be packaged and deployed somewhere. Boot's signature convenience here is the executable "fat jar" - a single file containing your compiled code, every dependency, and an embedded server, runnable with a single command and no separate application server to install. This lesson covers building that jar, running it, and wrapping it in a minimal Docker image.
- What an executable Spring Boot jar actually contains.
- How to build it with mvn package.
- How to run it with java -jar.
- How to override configuration at runtime without rebuilding.
- How to containerize the jar with a simple Dockerfile.
The Executable Jar
A plain Java jar built by Maven normally contains only your own compiled classes - it cannot run on its own because it has no idea how to find its dependencies. The spring-boot-maven-plugin repackages that jar into an executable one that bundles every dependency jar inside it, along with an embedded Tomcat (or Jetty/Undertow) server, so the whole application runs with nothing installed on the target machine except a JVM.
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>If your pom.xml inherits from spring-boot-starter-parent, as most Spring Boot projects do, the plugin is already configured and you do not need to add it manually.
Building with mvn package
Run mvn package (or mvn clean package to guarantee a fresh build) from the project root. Maven compiles the code, runs the tests, and produces the executable jar in the target directory.
mvn clean packageClick Run to see what this code prints.
Two artifacts actually land in target/: the executable jar and a much smaller *.jar.original file, which is the plain jar before Boot's repackaging step. Only the executable jar is meant to be run.
Running the Jar
With the jar built, start the application with java -jar, exactly the same way in any environment that has a matching JVM installed.
java -jar target/task-manager-0.0.1-SNAPSHOT.jarClick Run to see what this code prints.
Overriding Configuration at Runtime
Because Boot layers configuration sources with command-line arguments taking the highest priority, you can change behavior without rebuilding the jar at all - useful for setting the port, active profile, or any property per environment.
java -jar target/task-manager-0.0.1-SNAPSHOT.jar \ --server.port=9090 \ --spring.profiles.active=prodThe same effect can be achieved with environment variables (SERVER_PORT=9090, SPRING_PROFILES_ACTIVE=prod), which is often the more natural fit in containerized deployments.
A Simple Docker Image
Wrapping the jar in a Docker image makes it deployable anywhere Docker runs, with a consistent, self-contained environment. A minimal Dockerfile just needs a JRE base image and the built jar.
# DockerfileFROM eclipse-temurin:21-jre-alpineWORKDIR /appCOPY target/task-manager-0.0.1-SNAPSHOT.jar app.jarEXPOSE 8080ENTRYPOINT ["java", "-jar", "app.jar"]docker build -t task-manager:latest .docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE=prod task-manager:latestClick Run to see what this code prints.
For repeated Docker builds, Spring Boot's layered jar support (splitting dependencies from application code into separate image layers) speeds up rebuilds significantly, since unchanged dependency layers are cached. It is a worthwhile next step once basic Docker packaging feels routine.
Common Mistakes
- Running the *.jar.original file by mistake instead of the repackaged executable jar.
- Forgetting mvn clean before package, which can leave stale classes from a previous build.
- Hard-coding environment-specific values in application.properties instead of overriding them at runtime.
- Building a Docker image from a full JDK base image instead of a smaller JRE image, bloating the image unnecessarily.
- Not exposing (or mismatching) the port between the container and the -p flag when running Docker.
Best Practices
- Use mvn clean package (or your CI pipeline's equivalent) for every real build.
- Override environment-specific settings with command-line args or environment variables rather than baking them into the jar.
- Use a minimal JRE (not JDK) base image for Docker builds to keep images small.
- Pin the Spring Boot and Java versions your image uses so builds stay reproducible.
- Adopt layered jars once your Docker build times start to matter.
Frequently Asked Questions
No. The embedded server (Tomcat by default) is already bundled inside the executable jar, so nothing beyond a JVM needs to be installed on the machine running it.
Yes - the same applies with Gradle, using the org.springframework.boot Gradle plugin and the bootJar task, which produces an equivalent executable jar.
It bundles every dependency your application uses, plus the embedded server, all inside one file. This is the tradeoff for the convenience of a single deployable artifact with no external dependency management at runtime.
Key Takeaways
- spring-boot-maven-plugin repackages your build into a self-contained executable jar.
- mvn clean package builds it; java -jar runs it, with no separate app server required.
- Command-line arguments and environment variables override application.properties at runtime.
- A minimal Dockerfile just needs a JRE base image, the jar, and an ENTRYPOINT.
- Layered jars speed up repeated Docker builds by caching unchanged dependency layers.
Summary
Spring Boot turns deployment into a single self-contained jar that runs anywhere a JVM is available, and that same jar drops cleanly into a minimal Docker image for container-based deployments. With packaging covered, the final lessons turn to the mistakes to avoid and a complete real-world project that ties everything in this course together.