Skip to content

Packaging

A jar_package builds a jar. The other Java archives, a war, an ear or a Spring Boot executable jar, are jars with a different layout: the same classes and dependency jars, placed under different directories. Fabr builds the war and ear layouts with war_package and ear_package, a Spring Boot executable jar with spring_boot_jar, and any other layout with a rule you write yourself out of the same pieces.

With Maven or Gradle In fabr
maven-jar-plugin / jar { } A jar_package.
<manifestEntries> / jar.manifest.attributes A jar_package’s manifest.
<packaging>war</packaging> / the war plugin A war_package.
<packaging>ear</packaging> / the ear plugin An ear_package.
maven-dependency-plugin:copy-dependencies A java_installation.
spring-boot-maven-plugin / the Spring Boot Gradle plugin A spring_boot_jar.
mvn javadoc:javadoc / gradle javadoc fabr doc app; see Javadoc and sources.
maven-source-plugin / withSourcesJar() fabr sources app; a sync publishes it with auto_attach.
target/app.jar / build/libs/app.jar Kept in fabr’s cache; fabr cp app ./out copies it out.
mvn deploy A sync target; see Publishing.

A jar_package builds one jar, named after the target: greeter.jar, or slf4j-api.jar for a target named org.slf4j/slf4j-api. It holds:

  • the classes the srcs compile to;
  • the resources, each under the name it has in the target (resources = src/main/resources:** puts src/main/resources/application.properties at application.properties);
  • META-INF/MANIFEST.MF.

A jar or zip among the resources is stored uncompressed, so that a nested jar can be read in place, as a Spring Boot loader reads one. The jar holds no pom.xml or META-INF/maven files.

The jar doesn’t carry its dependencies: whatever depends on the target gets the jar and its deps, as Maven’s consumers get a jar and its POM. To put them in an archive, see The jars a program needs.

The manifest has Manifest-Version and Created-By: fabr, then Main-Class where the target has a main_class, then the target’s manifest entries in the order they are written:

jar_package app {
srcs = src/main/java:**/*.java;
main_class = com.example.Main;
manifest = {
Implementation-Version = 1.4.0;
Automatic-Module-Name = com.example.app;
};
}

main_class must be one of the jar’s classes or a class of a package it requires, which is checked when the jar is built: app's main_class 'com.example.Mian' is no class of its sources or of what it requires. A Main-Class written in manifest instead is not checked, which is how a jar can name a launcher that is among its resources; a jar can’t name its main class both ways.

fabr doc app generates a jar_package’s or a war_package’s javadoc, and fabr sources app gives its sources: its srcs, the sources its annotation processors generate, and its resources (a war’s webapp is not among them), laid out as a sources jar holds them. A sync can publish both beside the artifact; see Sources and javadoc jars. Both include the generated sources, such as AutoValue’s AutoValue_Point.java, because they are part of the jar’s classes; the jar itself holds no sources. Each is a tree of files, which you look at as you would a build’s:

Terminal window
fabr doc app
fabr cp 'app<BUILD_OPERATION=doc>' ./out # ./out/app/index.html, …
fabr ls 'app<BUILD_OPERATION=sources>' # com/example/Main.java, …

The javadoc is generated by JAVADOC, from the JDK release unless you set it, for the release the jar is compiled for, against the same classpath, or module path, as the compile. Its warnings, such as warning: no comment, are shown with the step; an error in a doc comment fails it, as it fails javadoc.

The javadoc covers the sources the jar’s annotation processors generate too, such as AutoValue’s AutoValue_Point. What Lombok adds is missing: Lombok changes the classes as they are compiled rather than writing sources, and javadoc reads sources. That is so in Maven and Gradle too, where a project documents Lombok code by running Lombok’s delombok command first and giving javadoc its output.

A javadoc target generates javadoc for any Java sources, against the packages its deps name. Javadoc documents the .java files among its srcs and reads the rest as it would from a source directory: a package’s package.html, an overview.html, and a doc-files directory, which it copies into the output. One javadoc target over several jars’ sources gives one site for all of them, with links between them, as Maven’s javadoc:aggregate does:

javadoc api_docs {
srcs = api<BUILD_OPERATION=sources> impl<BUILD_OPERATION=sources>;
deps = api impl;
}

srcs are the jars’ sources, generated sources included, and deps puts the jars, with everything they require, on the classpath.

A dependency’s own javadoc and sources are read the same way; see Dependencies.

A java_installation is the jars of everything its deps require, each under its own file name, in one directory: what Maven’s copy-dependencies produces.

java_installation libs {
deps = @mvn:org.apache.commons:commons-text:1.11.0;
}

builds commons-text-1.11.0.jar and commons-lang3-3.13.0.jar. Loose files among its deps are kept as they are. It is the piece the archive layouts below are made of: placed under a directory of an archive, with a rename, it is that archive’s library directory.

A package that something requires only as a provided_deps is not installed: whatever runs the installation supplies it, as Maven and Gradle leave provided and compileOnly dependencies out. An installation’s own provided_deps name what that is. They are resolved with its deps, so the two use one version of each package, and left out together with everything they require, however the deps reach them, as Gradle’s war plugin leaves out providedCompile:

java_installation libs {
deps = app;
provided_deps = @mvn:jakarta.platform:jakarta.jakartaee-api:10.0.0;
}

A war_package builds <name>.war:

In the war From
WEB-INF/classes/ The classes the srcs compile to, and the resources.
WEB-INF/lib/ The jars of everything deps require.
The root The webapp files: pages, static content, WEB-INF/web.xml.
war_package shop {
srcs = src/main/java:**/*.java;
resources = src/main/resources:**;
webapp = src/main/webapp:**;
deps = @mvn:org.apache.commons:commons-text:1.11.0;
provided_deps = @mvn:jakarta.servlet:jakarta.servlet-api:6.0.0;
}

provided_deps are compiled against and left out of WEB-INF/lib, with everything they require, for the servlet API and anything else the container supplies. A package a jar_package among the deps requires as a provided_deps is left out too, unless the war’s deps require it. With the Maven layout above, src/main/webapp/WEB-INF/web.xml is the war’s WEB-INF/web.xml; a descriptor kept elsewhere is placed with a rename:

webapp = src/main/webapp:** config:web.xml -> WEB-INF/web.xml;

The application’s own code can also come from a jar_package among the deps, with no srcs: its jar then goes in WEB-INF/lib beside its dependencies.

An ear_package builds <name>.ear, holding its modules, the wars and jars it deploys, at the root, the jars of everything deps require under lib/, and its resources at the root:

ear_package store {
modules = shop;
deps = @mvn:org.apache.commons:commons-lang3:3.14.0;
resources = src/main/application:**;
}

modules = shop puts shop.war in the ear. Fabr doesn’t write a META-INF/application.xml, which Java EE 5 and later don’t require; to give one, put it among the resources.

A Spring Boot executable jar holds an application and every jar it needs, and starts with java -jar: the jars, uncompressed, under BOOT-INF/lib/, and the Spring Boot loader’s classes at the root, whose JarLauncher starts the class the manifest names as Start-Class. A spring_boot_jar builds one from a jar_package. Its target type comes in an optional file of the Java plugin, which you include:

include @fabr-build/java:lib/spring-boot.fabr;
jar_package server {
srcs = src/main/java:**/*.java;
resources = src/main/resources:**;
deps = @mvn:org.springframework.boot:spring-boot-dependencies:4.1.2
@mvn:org.springframework.boot:spring-boot-starter-webmvc
java21;
main_class = com.example.shop.ShopApplication;
}
spring_boot_jar shop {
app = server;
main_class = com.example.shop.ShopApplication;
loader = @mvn:org.springframework.boot:spring-boot-loader:4.1.2;
}

fabr build shop builds shop.jar, holding server.jar and the jar of everything it requires under BOOT-INF/lib/. fabr run server runs the same application from its jars, for development.

Property What it is
app The application: a jar_package, with everything it requires.
main_class The class the application starts in.
loader spring-boot-loader, of the application’s Spring Boot release.
launcher The loader’s class that starts the jar. The default, org.springframework.boot.loader.launch.JarLauncher, is Spring Boot 3.2 and later’s; before 3.2 it is org.springframework.boot.loader.JarLauncher.

The type and its rule are written in .fabr, in the Java plugin’s lib/spring-boot.fabr, out of a java_installation and a jar_package; A layout of your own explains how such a rule is written. The jar differs from the one Spring Boot’s own plugins write in three ways:

  • The application’s classes are in its jar under BOOT-INF/lib/, not under BOOT-INF/classes/. Spring Boot finds them either way; a tool that reads the layout, such as java -Djarmode=tools, expects BOOT-INF/classes/. The layout below puts them there.
  • There is no BOOT-INF/classpath.idx, so Spring Boot puts the jars on the classpath in the order they are stored in, which is by name. This matters only where two jars hold the same class.
  • There is no layers.idx for layered container images.

A layout fabr doesn’t build is a rule you write in your build file. As an example, this one builds a Spring Boot executable jar from the application’s sources, with its classes under BOOT-INF/classes/ as Spring Boot’s own plugins put them. Declare a target type for it, and a rule that builds it from a java_compile, a java_installation and a jar_package:

targetdef boot_jar {
# The application's Java sources.
srcs = FILES;
# The packages the application uses, and `java<release>` flags.
deps = PACKAGES;
# Files on the application's classpath, such as `application.properties`.
resources = FILES;
# The application's main class.
main_class = REQUIRED STRING;
# The Spring Boot loader: `spring-boot-loader`, of the application's Spring Boot release.
loader = PACKAGES;
}
rule boot_jar b<BUILD_OPERATION=build> = jar {
java_compile classes {
srcs = b.srcs;
deps = b.deps;
}
java_installation libs {
deps = b.deps;
}
jar_package jar {
resources = classes:classes/** -> BOOT-INF/classes/** b.resources:** -> BOOT-INF/classes/**
libs:** -> BOOT-INF/lib/** b.loader:*.jar:org/**;
manifest = {
Main-Class = org.springframework.boot.loader.JarLauncher;
Start-Class = ${b.main_class};
Spring-Boot-Classes = BOOT-INF/classes/;
Spring-Boot-Lib = BOOT-INF/lib/;
};
}
}
boot_jar demo {
srcs = src/main/java:**/*.java;
resources = src/main/resources:**;
deps = @mvn:org.springframework.boot:spring-boot-dependencies:2.7.18
@mvn:org.springframework.boot:spring-boot-starter
java17;
main_class = demo.DemoApplication;
loader = @mvn:org.springframework.boot:spring-boot-loader:2.7.18;
}

fabr build demo builds demo.jar, and java -jar demo.jar starts the application.

  • A java_compile produces the classes under classes/ and any sources its annotation processors generate under generated/, so classes:classes/** -> BOOT-INF/classes/** places the classes under BOOT-INF/classes/; libs:** -> BOOT-INF/lib/** does the same for the dependency jars, which jar_package stores uncompressed because they are jars.
  • b.loader:*.jar:org/** reads inside the loader’s jar and takes its classes, leaving out its own manifest, so they sit at the root of demo.jar.
  • Main-Class is written in manifest rather than as main_class, because the launcher is one of the jar’s resources, not one of its classes.
  • From Spring Boot 3.2 the launcher is org.springframework.boot.loader.launch.JarLauncher.

The jar has no BOOT-INF/classpath.idx, which fixes the order of the jars on the classpath, and no layers.idx for layered container images. Writing your own rules explains the rule’s parts.

  • The javadoc doesn’t link to the JDK’s documentation or to the dependencies’ javadoc.