Packaging & Running
Packaging & Running
Writing correct code is only half the job. A professional Java application must be reliably buildable, transportable, and executable on any target machine — from a colleague's laptop to a production server. This lesson covers the full packaging pipeline: compiling with Maven or Gradle, producing executable JARs, managing the runtime environment, and running the finished artifact both locally and on a server.
Understanding the Build Lifecycle
Both Maven and Gradle model builds as a sequence of phases. Understanding the lifecycle prevents the common mistake of running the wrong goal at the wrong time.
Maven lifecycle phases (ordered):
validate— checks the project is correct and all required information is present.compile— compiles source code intotarget/classes.test— runs unit tests via Surefire; the build fails if any test fails.package— bundles compiled classes into a JAR (or WAR) intarget/.verify— runs integration checks (e.g. Failsafe plugin).install— installs the artifact into the local Maven repository (~/.m2).deploy— pushes the artifact to a remote repository (Nexus, Artifactory, GitHub Packages).
mvn package automatically runs validate, compile, and test first. If you only want to compile without testing, use mvn package -DskipTests — but do that only on a developer workstation, never in CI.
Building an Executable Fat JAR with Maven
A standard JAR contains only your own classes. To run it anywhere without a separate classpath, you need a fat JAR (also called an uber JAR) — your classes plus every dependency bundled into one file. The Maven Shade plugin is the standard way to produce one.
After adding this plugin, run:
Building with Gradle
Gradle's equivalent is the shadowJar plugin (com.github.johnrengelman.shadow). Add it to build.gradle:
Then build and run:
clean before every release build. Stale class files from a previous compilation can slip into the JAR unnoticed, causing subtle bugs that never reproduce from a fresh checkout. Make clean package (or clean shadowJar) a habit in CI.
The MANIFEST.MF File
The entry point of an executable JAR is declared in META-INF/MANIFEST.MF inside the archive. The critical line is:
If this line is missing or points to a class without a public static void main(String[] args) method, the JVM throws java.lang.NoSuchMethodError: main or "Main manifest attribute not found". Both plugins above write this file automatically when you declare the mainClass.
Externalising Configuration at Runtime
A JAR built once must run in development, staging, and production without recompilation. Externalise all environment-specific values:
Pass system properties at runtime:
jar tf or unzip. Use environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault) for all sensitive configuration.
Loading a Properties File at Startup
For non-sensitive configuration, a .properties file loaded at startup is cleaner than a long list of -D flags:
JVM Tuning Flags for Production
The default JVM settings are calibrated for developer machines. In production, tune at minimum:
Running as a Background Service (Linux systemd)
On a Linux server, wrap the JAR in a systemd unit so it starts on boot and restarts on failure:
Smoke-Testing the Packaged Artifact
Before deploying, always verify the JAR works from a clean directory — not from your IDE's working directory:
This catches the most common packaging mistake: a resource or file that your IDE exposes on the classpath during development but that is absent from the bundled JAR.
Summary
A production-ready Java artifact is built with mvn clean package (Shade plugin) or ./gradlew clean shadowJar. The MANIFEST.MF declares the entry point; all environment-specific configuration is externalised via system properties, environment variables, or a side-car properties file. JVM heap flags and a systemd unit complete the deployment story. Smoke-test the JAR from a clean directory before every release to catch classpath-only resources early.