Packaging a single jar
A server has no Maven and no sources — you put a ready program there. The easiest way is to build an executable jar with all dependencies inside (called a fat jar or uber jar). Such a file starts with one java -jar command, and Jetty travels inside it — no separate web server is needed on the machine.
Why an ordinary jar is not enough
The mvn package command creates a jar even without plugins, but it contains only your classes. Javalin, Jetty, Jackson and H2 live in the local Maven repository on your computer, and the server does not have them. The maven-shade-plugin unpacks all dependencies and puts them into one file together with your code.
The plugin
Add the plugin to the plugins section of pom.xml:
<!-- pom.xml -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.blog.Main</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>| Part | Why |
|---|---|
phase package | The plugin runs during mvn package |
ManifestResourceTransformer | Writes the main class into the jar manifest, otherwise java -jar does not know what to run |
ServicesResourceTransformer | Merges the META-INF/services files through which Jetty and drivers find their parts |
The port from the environment
On a server the port is often set with an environment variable. Make main read PORT and use 8080 by default:
// src/main/java/com/example/blog/Main.java
public static void main(String[] args) {
var repo = new PostRepository(Database.create("jdbc:h2:./blog"));
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
createApp(repo).start(port);
}Building and running
mvn packageMaven runs the tests and builds the file target/blog-1.0.0.jar, about 12 MB in size. During the build the plugin prints many Discovered module-info.class and overlapping resource warnings — they are harmless for our application. Run the jar:
java -jar target/blog-1.0.0.jar[main] INFO io.javalin.Javalin - Javalin started in 176ms \o/
[main] INFO io.javalin.Javalin - Listening on http://localhost:8080/
On another port: PORT=9000 java -jar target/blog-1.0.0.jar. To deploy, copy this file to the server and install Java 25. The database file blog.mv.db appears in the folder the command was run from.
What next
To keep the application running and restart it after failures, it is run as a service (systemd on Linux) or in a Docker container based on a Java image such as eclipse-temurin:25-jre. In both cases the start command is the same — java -jar blog-1.0.0.jar.
Common mistake
Next to the right file the plugin leaves original-blog-1.0.0.jar — an ordinary jar without dependencies and without a main class. If you run it, Java reports at once:
no main manifest attribute, in target\original-blog-1.0.0.jar
And if you name the class by hand with java -cp, the program fails on the first Javalin class: NoClassDefFoundError: io/javalin/rendering/FileRenderer. Run the file without the original- prefix.