Running and Packaging the App

Run a Boot application from the build tool or IDE, package it as an executable jar, and pass configuration when you start it.

Syntax./mvnw spring-boot:run ./mvnw package java -jar target/orders-0.0.1-SNAPSHOT.jar --server.port=9090

During development you run the application in one of three ways: the IDE's run button on the main class, ./mvnw spring-boot:run, or ./gradlew bootRun. All three compile the code and start main with the project's classpath. The build-tool commands are the ones to put in a README, because they work the same for everyone.

The executable jar

./mvnw package (or ./gradlew build) runs the tests and produces a single jar in target/ or build/libs/. ./gradlew bootJar builds the jar without running the tests. It is not an ordinary jar: your classes sit in BOOT-INF/classes, every dependency is a nested jar in BOOT-INF/lib, and a small Boot launcher loads them. Run it with java -jar on any machine with a suitable JDK. That one file is the unit you deploy, copy into a container image or roll back to.

Maven also leaves *.jar.original, the plain jar before repackaging, and Gradle builds a -plain.jar. Neither runs on its own; make sure deployment scripts pick up the right file.

Passing configuration at start-up

Never rebuild to change a setting. Command-line arguments such as --server.port=9090 and environment variables such as SERVER_PORT=9090 override application.properties. Environment variables are the norm in containers; the relaxed binding rules turn SPRING_DATASOURCE_URL into spring.datasource.url.

Common mistakes

  • Running on an older JDK than you compiled for gives UnsupportedClassVersionError. Keep the runtime image in step with java.version.
  • Skipping tests with -DskipTests to get a jar "quickly" and then shipping it. Do that locally if you must, never in the pipeline.
  • Copying the fat jar into a container as one layer. Every code change then re-uploads every dependency. Extract it into layers instead, as the Dockerfile example shows.

Stopping cleanly

Orchestrators stop applications with SIGTERM. With graceful shutdown, which is the default for the embedded servers, the server stops accepting new requests and gives in-flight ones time to finish before the context closes. The wait is capped by spring.lifecycle.timeout-per-shutdown-phase (30 seconds by default); keep it shorter than your platform's kill grace period, so the JVM exits on its own terms.

Example

Example · bash

# Development: compile and run with the project's classpath.
./mvnw spring-boot:run

# Build the executable jar (runs the tests first).
./mvnw clean package

# Run it anywhere with a Java 25 runtime.
java -jar target/orders-0.0.1-SNAPSHOT.jar

# Override configuration without rebuilding.
java -jar target/orders-0.0.1-SNAPSHOT.jar --server.port=9090 --spring.profiles.active=prod
SERVER_PORT=9090 SPRING_PROFILES_ACTIVE=prod java -jar target/orders-0.0.1-SNAPSHOT.jar

When to use it

  • Hand operations a single jar that runs with java -jar and is configured through environment variables.
  • Run the same build on port 9090 locally while another service already uses 8080.
  • Cut image push times in CI by layering dependencies separately from application classes.

More examples

The plugin that builds the executable jar

The Spring Boot Maven plugin repackages the plain jar into the executable one during package and provides the spring-boot:run goal. Its version comes from the starter parent. Setting finalName gives the jar a stable name, target/orders.jar, so deployment scripts and Dockerfiles do not need a wildcard that might match the .original file.

Example · xml
<build>
    <finalName>orders</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Shutting down without dropping requests

On SIGTERM the server stops taking new connections and waits up to 20 seconds for active requests to complete, then the context closes beans in order. The default phase timeout is 30 seconds, the same as Kubernetes' default grace period, which leaves no margin: if the wait outlasts the grace period, the process is killed mid-shutdown and clients see reset connections anyway.

Example · properties
# Graceful is already the default; stating it documents the intent.
server.shutdown=graceful
# Must be shorter than the platform's grace period (30 s by default in Kubernetes).
spring.lifecycle.timeout-per-shutdown-phase=20s

A layered container image

Dependencies change rarely and your code changes on every commit, so copying them as separate layers means a typical rebuild only pushes the small application layer. The tools jar mode is built into every Boot executable jar. The Deploying stage covers images in depth, including Buildpacks, which produce a layered image with no Dockerfile at all.

Example · dockerfile
FROM eclipse-temurin:25-jre AS builder
WORKDIR /builder
COPY target/orders.jar application.jar
# Split the jar into dependency and application layers.
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

FROM eclipse-temurin:25-jre
WORKDIR /application
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
ENTRYPOINT ["java", "-jar", "application.jar"]

Discussion

  • Be the first to comment on this lesson.