Producing a launcher jar

You never assemble a launcher jar by hand. The Jenesis build tool produces it from one switch in packaging.properties: it resolves the launcher, copies its classes into the jar, lays out your dependencies for it, and writes the descriptor and manifest that make java -jar app.jar start the launcher. This chapter shows that switch, what the build writes, and where the result lands.

Turning it on

The launcher jar is one of the build tool's packaging options. Enable it with launcher=true in a packaging.properties file in the configuration folder:

# build.jenesis/packaging.properties
launcher=true
java build/jenesis/Make.java

Like every packaging feature, it only runs for a module that declares a main class - the same @jenesis.main tag (or <mainClass> POM property) the other packaging steps key off. A module without one is skipped, so a library is left alone and an application needs no launcher-specific configuration.

What the build writes

The build resolves the published launcher artifact and produces the jar in five moves. Everything the launcher needs at run time - the layout described in How it works - is put in place here:

  1. The launcher's classes go into the jar root. Only its build/jenesis/launcher/*.class files are copied; the launcher's own module-info and manifest are left out, so at run time those classes are the unnamed module that hosts your application.
  2. Each dependency is exploded into its own subfolder of one jars/ store. The resolved jar file name becomes the folder name: jars/org.slf4j-2.0.16.jar/, and the application's own jar is named the same way, jars/demo.bundle-0-SNAPSHOT.jar/. Every jar is stored once, whatever it is for.
  3. application.properties is written with mainClass, mainModule (modular applications only), and the two path lists, classpath and modulepath - because a jar is read on the path that names it, never because of where it sits. Which list a jar lands in follows the same rule as the build's Execute launcher and its bundle.zip: a jar is on the module path only when the application is modular and the jar describes a module, so a pom.xml application without a module has everything on its class path. A project that declares a module layer gets a modulepath.<layer> list beside them, and a classpath.<layer> when the layer holds jars that name no module.
  4. The manifest names the launcher as Main-Class: build.jenesis.launcher.Launcher, so java -jar starts the launcher.
  5. The jar gets a bill of materials of its own when the module gets one (see Supply-chain features): a CycloneDX document at META-INF/sbom/<artifact>.cdx.json, which the manifest names with Sbom-Format and Sbom-Location. It lists what the module's document lists and adds the launcher as a dependency of the project, which it describes as an application. The module's own document stays in the folder of the application's own jar.

That is the complete set. The other descriptor keys and manifest attributes the launcher understands - bundled agents, module-access grants, signer reconstruction - are for a jar you assemble yourself; the Reference chapter lists them.

A bundle (bundle=true) names the same two paths, but keeps each jar whole in its jars/ folder for you to drop onto a JRE. The launcher jar explodes those same jars into subfolders and adds the launcher, so it needs no launch script.

Where the jar lands

The jar is named after the module's artifact id - demo.modular.executable.jar for the modular demo - or application.jar when the build knows no artifact id. It is written into the module's build output, under the launcher module's bundle step:

target/build/…/launcher/bundle/output/launcher/<name>.jar

It is not collected into the stage tree. A build of your own can locate it as the jar under a launcher/ folder of the build output - build/DemoLauncher.java in the two executable demos does exactly that, then runs the jar.

The launcher is pinned like any dependency

The build resolves the launcher as a normal dependency, in its own launcher group, kept apart from your application's dependencies. Until you pin it, it floats to the latest release. Run pin and the build records the exact version and checksum next to your other pins - in a modular project:

/**
 * @jenesis.main sample.Sample
 * @jenesis.pin launcher/maven/build.jenesis/build.jenesis.launcher 0.4.0 SHA-256/e56603eb…
 */
module demo.modular.executable {
    requires org.slf4j;

    exports sample;
}

A pom.xml project carries the same line in its <!--jenesis.pin … --> block, outside <dependencyManagement>, since the launcher is not an application dependency. Either way the launcher bytes shaded into your jar are verified on every build, and the produced jar is reproducible: the same sources yield the same bytes.

With the jar produced, the next chapter turns to running it: the start-up flow, what the single loader means for your code, and the pitfalls to watch for.