Appendix E. The executable jar format

The spring-boot-loader modules allows Spring Boot to support executable jar and
war files. If you’re using the Maven or Gradle plugin, executable jars are
automatically generated and you generally won’t need to know the details of how
they work.

If you need to create executable jars from a different build system, or if you are just
curious about the underlying technology, this section provides some background.

E.1 Nested JARs

Java does not provide any standard way to load nested jar files (i.e. jar files that
are themselves contained within a jar). This can be problematic if you are looking
to distribute a self-contained application that you can just run from the command line
without unpacking.

To solve this problem, many developers use “shaded” jars. A shaded jar simply packages
all classes, from all jars, into a single 'uber jar'. The problem with shaded jars is
that it becomes hard to see which libraries you are actually using in your application.
It can also be problematic if the same filename is used (but with different content)
in multiple jars. Spring Boot takes a different approach and allows you to actually nest
jars directly.

E.1.1 The executable jar file structure

Spring Boot Loader compatible jar files should be structured in the following way:

Dependencies should be placed in a nested WEB-INF/lib directory. Any dependencies
that are required when running embedded but are not required when deploying to
a traditional web container should be placed in WEB-INF/lib-provided.

E.2 Spring Boot’s “JarFile” class

The core class used to support loading nested jars is
org.springframework.boot.loader.jar.JarFile. It allows you to load jar
content from a standard jar file, or from nested child jar data. When first loaded, the
location of each JarEntry is mapped to a physical file offset of the outer jar:

The example above shows how A.class can be found in /BOOT-INF/classes in myapp.jar
position 0063. B.class from the nested jar can actually be found in myapp.jar
position 3452 and C.class is at position 3980.

Armed with this information, we can load specific nested entries by simply seeking to
the appropriate part of the outer jar. We don’t need to unpack the archive and we
don’t need to read all entry data into memory.

E.2.1 Compatibility with the standard Java “JarFile”

Spring Boot Loader strives to remain compatible with existing code and libraries.
org.springframework.boot.loader.jar.JarFile extends from java.util.jar.JarFile and
should work as a drop-in replacement. The getURL() method will return a URL that
opens a java.net.JarURLConnection compatible connection and can be used with Java’s
URLClassLoader.

E.3 Launching executable jars

The org.springframework.boot.loader.Launcher class is a special bootstrap class that
is used as an executable jars main entry point. It is the actual Main-Class in your jar
file and it’s used to setup an appropriate URLClassLoader and ultimately call your
main() method.

There are 3 launcher subclasses (JarLauncher, WarLauncher and PropertiesLauncher).
Their purpose is to load resources (.class files etc.) from nested jar files or war
files in directories (as opposed to explicitly on the classpath). In the case of
JarLauncher and WarLauncher the nested paths are fixed. JarLauncher looks in
BOOT-INF/lib/ and WarLauncher looks in WEB-INF/lib/ and WEB-INF/lib-provided/ so
you just add extra jars in those locations if you want more. The PropertiesLauncher
looks in BOOT-INF/lib/ in your application archive by default, but you can add
additional locations by setting an environment variable LOADER_PATH or loader.path
in loader.properties (comma-separated list of directories, archives, or directories
within archives).

E.3.1 Launcher manifest

You need to specify an appropriate Launcher as the Main-Class attribute of
META-INF/MANIFEST.MF. The actual class that you want to launch (i.e. the class that
you wrote that contains a main method) should be specified in the Start-Class
attribute.

For example, here is a typical MANIFEST.MF for an executable jar file:

You do not need to specify Class-Path entries in your manifest file, the classpath
will be deduced from the nested jars.

E.3.2 Exploded archives

Certain PaaS implementations may choose to unpack archives before they run. For example,
Cloud Foundry operates in this way. You can run an unpacked archive by simply starting
the appropriate launcher:

E.4 PropertiesLauncher Features

PropertiesLauncher has a few special features that can be enabled with external
properties (System properties, environment variables, manifest entries or
loader.properties).

Note

PropertiesLauncher supports loading properties from
loader.properties and also (for historic reasons)
application.properties. We recommend using
loader.properties exclusively, as support for
application.properties is deprecated and may be removed in the future.

Key

Purpose

loader.path

Comma-separated Classpath, e.g. lib,${HOME}/app/lib. Earlier entries take precedence,
just like a regular -classpath on the javac command line.

loader.home

Used to resolve relative paths in loader.path. E.g. loader.path=lib then
${loader.home}/lib is a classpath location (along with all jar files in that
directory). Also used to locate a loader.properties file. Example /opt/app
(defaults to ${user.dir}).

Boolean flag to indicate that all properties should be added to System properties
(defaults to false)

When specified as environment variables or manifest entries, the following names should
be used:

Key

Manifest entry

Environment variable

loader.path

Loader-Path

LOADER_PATH

loader.home

Loader-Home

LOADER_HOME

loader.args

Loader-Args

LOADER_ARGS

loader.main

Start-Class

LOADER_MAIN

loader.config.location

Loader-Config-Location

LOADER_CONFIG_LOCATION

loader.system

Loader-System

LOADER_SYSTEM

Tip

Build plugins automatically move the Main-Class attribute to Start-Class when
the fat jar is built. If you are using that, specify the name of the class to launch using
the Main-Class attribute and leave out Start-Class.

loader.properties are searched for in loader.home then in the root of the
classpath, then in classpath:/BOOT-INF/classes. The first location that exists is
used.

loader.home is only the directory location of an additional properties file
(overriding the default) as long as loader.config.location is not specified.

loader.path can contain directories (scanned recursively for jar and zip files),
archive paths, a directory within an archive that is scanned for jar files (for
example, dependencies.jar!/lib), or wildcard patterns (for the default JVM behavior).
Archive paths can be relative to loader.home, or anywhere in the file system with a
jar:file: prefix.

loader.path (if empty) defaults to BOOT-INF/lib (meaning a local directory or a
nested one if running from an archive). Because of this PropertiesLauncher behaves the
same as JarLauncher when no additional configuration is provided.

loader.path can not be used to configure the location of loader.properties (the
classpath used to search for the latter is the JVM classpath when PropertiesLauncher
is launched).

Placeholder replacement is done from System and environment variables plus the
properties file itself on all values before use.

The search order for properties (where it makes sense to look in more than one place)
is env vars, system properties, loader.properties, exploded archive manifest, archive
manifest.

E.5 Executable jar restrictions

There are a number of restrictions that you need to consider when working with a Spring
Boot Loader packaged application.

E.5.1 Zip entry compression

The ZipEntry for a nested jar must be saved using the ZipEntry.STORED method. This
is required so that we can seek directly to individual content within the nested jar.
The content of the nested jar file itself can still be compressed, as can any other
entries in the outer jar.

E.5.2 System ClassLoader

Launched applications should use Thread.getContextClassLoader() when loading classes
(most libraries and frameworks will do this by default). Trying to load nested jar
classes via ClassLoader.getSystemClassLoader() will fail. Please be aware that
java.util.Logging always uses the system classloader, for this reason you should
consider a different logging implementation.

E.6 Alternative single jar solutions

If the above restrictions mean that you cannot use Spring Boot Loader the following
alternatives could be considered: