Runtime & JVM

Assembling a small runtime image of a modular Vert.x application with jlink

This document will show you how to assemble a small runtime image of a modular Vert.x application with jlink.

What you need

  • A text editor or an IDE

  • Java 11 or higher

Create a project

Here is the content of the Maven pom.xml file you should be using:

Maven pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>io.vertx.howtos</groupId>
  <artifactId>jlink-howto</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <vertx.version>5.0.0.CR4</vertx.version>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-stack-depchain</artifactId>
        <version>${vertx.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-launcher-application</artifactId>
    </dependency>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-web</artifactId>
    </dependency>
  </dependencies>

  <build>
    <finalName>jlink-howto</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jlink-plugin</artifactId> (1)
        <version>3.2.0</version>
        <executions>
          <execution>
            <id>jlink</id>
            <goals>
              <goal>jlink</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <classifier>dist</classifier> (2)
          <launcher>server=io.vertx.howtos.jlink/io.vertx.howtos.jlink.CustomLauncher</launcher> (3)
          <addModules>jdk.jdwp.agent</addModules> (4)
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>
1 The Apache Maven JLink Plugin will take care of invoking the jlink command-line tool.
2 Mandatory classifier for the custom runtime image archive that will be attached as a Maven artifact.
3 server is the name of the launcher script to generate via jlink. This is optional.
4 Add the jdk.jdwp.agent module so that we can attach a remote debugger, if we need to.

Implementing the application

The server setup code fits into a single class, io.vertx.howtos.jlink.ServerVerticle:

package io.vertx.howtos.jlink;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.ext.web.Router;

import java.util.logging.Logger;

public class ServerVerticle extends AbstractVerticle {

  private static final Logger LOG = Logger.getLogger(ServerVerticle.class.getName());

  @Override
  public void start(Promise<Void> startPromise) {
    Router router = Router.router(vertx);

    router.get().respond(rc -> Future.succeededFuture("Hello World!"));

    vertx.createHttpServer()
      .requestHandler(router)
      .listen(8888)
      .onSuccess(http -> LOG.info("HTTP server started on port 8888"))
      .<Void>mapEmpty()
      .onComplete(startPromise);
  }
}

A custom application launcher provides a factory for the main verticle to deploy:

package io.vertx.howtos.jlink;

import io.vertx.core.Verticle;
import io.vertx.launcher.application.VertxApplication;
import io.vertx.launcher.application.VertxApplicationHooks;

import java.util.function.Supplier;

public class CustomLauncher extends VertxApplication implements VertxApplicationHooks {

  public static void main(String[] args) {
    CustomLauncher vertxApplication = new CustomLauncher(args);
    vertxApplication.launch();
  }

  public CustomLauncher(String[] args) {
    super(args);
  }

  @Override
  public Supplier<Verticle> verticleSupplier() {
    return ServerVerticle::new;
  }
}

Having the verticle factory in io.vertx.howtos.jlink.CustomLauncher simplifies the Java module configuration:

module io.vertx.howtos.jlink {
  requires io.vertx.core;
  requires io.vertx.launcher.application;
  requires io.vertx.web;
  requires io.vertx.auth.common;
  requires java.logging;
}

Indeed, since the verticle instance won’t be created via reflection, we don’t have to export our application package to the io.vertx.core module, we just have to declare a few required modules.

Building the application

On Linux or other Unix-like systems, run the following command:

./mvnw clean package

On Microsoft Windows:

mvnw.cmd clean package

Notice that all required modules are resolved before jlink creates the custom runtime image.

[INFO] --- jlink:3.2.0:jlink (jlink) @ jlink-howto ---
[INFO]  -> module: io.netty.handler ( /path/to/my/mavenrepo/io/netty/netty-handler/4.2.0.RC1/netty-handler-4.2.0.RC1.jar )
[INFO]  -> module: io.vertx.web ( /path/to/my/mavenrepo/io/vertx/vertx-web/5.0.0.CR4/vertx-web-5.0.0.CR4.jar )
[INFO]  -> module: io.vertx.core ( /path/to/my/mavenrepo/io/vertx/vertx-core/5.0.0.CR4/vertx-core-5.0.0.CR4.jar )
[INFO]  -> module: io.netty.handler.proxy ( /path/to/my/mavenrepo/io/netty/netty-handler-proxy/4.2.0.RC1/netty-handler-proxy-4.2.0.RC1.jar )
[INFO]  -> module: com.fasterxml.jackson.core ( /path/to/my/mavenrepo/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar )
[INFO]  -> module: io.netty.codec.unused ( /path/to/my/mavenrepo/io/netty/netty-codec/4.2.0.RC1/netty-codec-4.2.0.RC1.jar )
[INFO]  -> module: io.vertx.launcher.application ( /path/to/my/mavenrepo/io/vertx/vertx-launcher-application/5.0.0.CR4/vertx-launcher-application-5.0.0.CR4.jar )
[INFO]  -> module: io.vertx.auth.common ( /path/to/my/mavenrepo/io/vertx/vertx-auth-common/5.0.0.CR4/vertx-auth-common-5.0.0.CR4.jar )
[INFO]  -> module: info.picocli ( /path/to/my/mavenrepo/info/picocli/picocli/4.7.4/picocli-4.7.4.jar )
[INFO]  -> module: io.vertx.howtos.jlink ( /path/to/my/Projects/vertx-howtos/jlink-howto/target/classes )
[INFO]  -> module: io.netty.transport.unix.common ( /path/to/my/mavenrepo/io/netty/netty-transport-native-unix-common/4.2.0.RC1/netty-transport-native-unix-common-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec.compression ( /path/to/my/mavenrepo/io/netty/netty-codec-compression/4.2.0.RC1/netty-codec-compression-4.2.0.RC1.jar )
[INFO]  -> module: io.vertx.web.common ( /path/to/my/mavenrepo/io/vertx/vertx-web-common/5.0.0.CR4/vertx-web-common-5.0.0.CR4.jar )
[INFO]  -> module: io.netty.buffer ( /path/to/my/mavenrepo/io/netty/netty-buffer/4.2.0.RC1/netty-buffer-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec.http2 ( /path/to/my/mavenrepo/io/netty/netty-codec-http2/4.2.0.RC1/netty-codec-http2-4.2.0.RC1.jar )
[INFO]  -> module: io.vertx.core.logging ( /path/to/my/mavenrepo/io/vertx/vertx-core-logging/5.0.0.CR4/vertx-core-logging-5.0.0.CR4.jar )
[INFO]  -> module: io.netty.common ( /path/to/my/mavenrepo/io/netty/netty-common/4.2.0.RC1/netty-common-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.resolver.dns ( /path/to/my/mavenrepo/io/netty/netty-resolver-dns/4.2.0.RC1/netty-resolver-dns-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec.http ( /path/to/my/mavenrepo/io/netty/netty-codec-http/4.2.0.RC1/netty-codec-http-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec.socks ( /path/to/my/mavenrepo/io/netty/netty-codec-socks/4.2.0.RC1/netty-codec-socks-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec.dns ( /path/to/my/mavenrepo/io/netty/netty-codec-dns/4.2.0.RC1/netty-codec-dns-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.transport ( /path/to/my/mavenrepo/io/netty/netty-transport/4.2.0.RC1/netty-transport-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.codec ( /path/to/my/mavenrepo/io/netty/netty-codec-base/4.2.0.RC1/netty-codec-base-4.2.0.RC1.jar )
[INFO]  -> module: io.netty.resolver ( /path/to/my/mavenrepo/io/netty/netty-resolver/4.2.0.RC1/netty-resolver-4.2.0.RC1.jar )
[INFO]  -> module: io.vertx.eventbusbridge ( /path/to/my/mavenrepo/io/vertx/vertx-bridge-common/5.0.0.CR4/vertx-bridge-common-5.0.0.CR4.jar )
[INFO] Building zip: /path/to/my/vertx-howtos/jlink-howto/target/jlink-howto-dist.zip

Running the application

Extract the target/jlink-howto-dist.zip archive somewhere on your disk. On Linux or other Unix-like systems, you can do this with the following command:

unzip -d target/jlink-howto target/jlink-howto-dist.zip

Inspect the total size of the custom runtime image folder. On Linux or other Unix-like systems, you can do this with the following command:

du -sh target/jlink-howto

On my machine, the result is 68 MB. In comparison, the total size of the JDK 11 distribution is 312 MB.

It’s time to give the application a try. On Linux or other Unix-like systems, you can do this with the following command:

target/jlink-howto/bin/java --module io.vertx.howtos.jlink/io.vertx.howtos.jlink.CustomLauncher

Or, you can use the generated server script:

target/jlink-howto/bin/server

You should see something like:

Feb 03, 2025 6:48:31 PM io.vertx.howtos.jlink.ServerVerticle lambda$start$1
INFO: HTTP server started on port 8888
Feb 03, 2025 6:48:31 PM io.vertx.launcher.application.VertxApplication
INFO: Succeeded in deploying verticle

Now, browse to http://localhost:8888 and verify that a greeting is displayed in the browser.

When the launcher script is used, it’s not possible to specify VM options (heap size, remote debugging, etc.) as arguments.

You can either change the JLINK_VM_OPTIONS variable in the server script or use the java command.

For example, for remote debugging on Linux or other Unix-like systems:

target/jlink-howto/bin/java \
  -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 \
  --module io.vertx.howtos.jlink/io.vertx.howtos.jlink.CustomLauncher

Summary

This document covered:

  1. creating a modular Vert.x Web application,

  2. assembling a custom runtime image with jlink.