On this page
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.
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.
Spring Boot Loader compatible jar files should be structured in the following way:
example.jar
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
+-dependency1.jar
+-dependency2.jar
Application classes should be placed in a nested BOOT-INF/classes
directory. Dependencies should be placed in a nested BOOT-INF/lib
directory.
Spring Boot Loader compatible war files should be structured in the following way:
example.war
|
+-META-INF
| +-MANIFEST.MF
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-WEB-INF
+-classes
| +-com
| +-mycompany
| +-project
| +-YourClasses.class
+-lib
| +-dependency1.jar
| +-dependency2.jar
+-lib-provided
+-servlet-api.jar
+-dependency3.jar
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
.
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:
myapp.jar
+-------------------+-------------------------+
| /BOOT-INF/classes | /BOOT-INF/lib/mylib.jar |
|+-----------------+||+-----------+----------+|
|| A.class ||| B.class | C.class ||
|+-----------------+||+-----------+----------+|
+-------------------+-------------------------+
^ ^ ^
0063 3452 3980
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.
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
.
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).
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:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.mycompany.project.MyApplication
For a war file, it would be:
Main-Class: org.springframework.boot.loader.WarLauncher
Start-Class: com.mycompany.project.MyApplication
Note | |
---|---|
You do not need to specify |
PropertiesLauncher
has a few special features that can be enabled with external properties (System properties, environment variables, manifest entries or loader.properties
).
Note | |
---|---|
|
Key | Purpose |
---|---|
|
Comma-separated Classpath, e.g. |
|
Used to resolve relative paths in |
|
Default arguments for the main method (space separated) |
|
Name of main class to launch, e.g. |
|
Name of properties file, e.g. |
|
Path to properties file, e.g. |
|
Boolean flag to indicate that all properties should be added to System properties (defaults to |
When specified as environment variables or manifest entries, the following names should be used:
Key | Manifest entry | Environment variable |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Tip | |
---|---|
Build plugins automatically move the |
loader.properties
are searched for inloader.home
then in the root of the classpath, then inclasspath:/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 asloader.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 toloader.home
, or anywhere in the file system with ajar:file:
prefix.loader.path
(if empty) defaults toBOOT-INF/lib
(meaning a local directory or a nested one if running from an archive). Because of thisPropertiesLauncher
behaves the same asJarLauncher
when no additional configuration is provided.loader.path
can not be used to configure the location ofloader.properties
(the classpath used to search for the latter is the JVM classpath whenPropertiesLauncher
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.
There are a number of restrictions that you need to consider when working with a Spring Boot Loader packaged application.
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.
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.
If the above restrictions mean that you cannot use Spring Boot Loader the following alternatives could be considered: