Packaging Executable Archives
The plugin can create executable archives (jar files and war files) that contain all of an application’s dependencies and can then be run with java -jar
.
Packaging Executable Jars
Executable jars can be built using the bootJar
task.
The task is automatically created when the java
plugin is applied and is an instance of BootJar
.
The assemble
task is automatically configured to depend upon the bootJar
task so running assemble
(or build
) will also run the bootJar
task.
Packaging Executable Wars
Executable wars can be built using the bootWar
task.
The task is automatically created when the war
plugin is applied and is an instance of BootWar
.
The assemble
task is automatically configured to depend upon the bootWar
task so running assemble
(or build
) will also run the bootWar
task.
Packaging Executable and Deployable Wars
A war file can be packaged such that it can be executed using java -jar
and deployed to an external container.
To do so, the embedded servlet container dependencies should be added to the providedRuntime
configuration, for example:
-
Groovy
-
Kotlin
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}
This ensures that they are package in the war file’s WEB-INF/lib-provided
directory from where they will not conflict with the external container’s own classes.
providedRuntime is preferred to Gradle’s compileOnly configuration as, among other limitations, compileOnly dependencies are not on the test classpath so any web-based integration tests will fail.
|
Packaging Executable and Plain Archives
By default, when the bootJar
or bootWar
tasks are configured, the jar
or war
tasks are configured to use plain
as the convention for their archive classifier.
This ensures that bootJar
and jar
or bootWar
and war
have different output locations, allowing both the executable archive and the plain archive to be built at the same time.
If you prefer that the executable archive, rather than the plain archive, uses a classifier, configure the classifiers as shown in the following example for the jar
and bootJar
tasks:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
archiveClassifier = 'boot'
}
tasks.named("jar") {
archiveClassifier = ''
}
tasks.named<BootJar>("bootJar") {
archiveClassifier.set("boot")
}
tasks.named<Jar>("jar") {
archiveClassifier.set("")
}
Alternatively, if you prefer that the plain archive isn’t built at all, disable its task as shown in the following example for the jar
task:
-
Groovy
-
Kotlin
tasks.named("jar") {
enabled = false
}
tasks.named<Jar>("jar") {
enabled = false
}
Do not disable the jar task when creating native images.
See #33238 for details.
|
Configuring Executable Archive Packaging
The BootJar
and BootWar
tasks are subclasses of Gradle’s Jar
and War
tasks respectively.
As a result, all of the standard configuration options that are available when packaging a jar or war are also available when packaging an executable jar or war.
A number of configuration options that are specific to executable jars and wars are also provided.
Configuring the Main Class
By default, the executable archive’s main class will be configured automatically by looking for a class with a public static void main(String[])
method in the main source set’s output.
The main class can also be configured explicitly using the task’s mainClass
property:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
mainClass = 'com.example.ExampleApplication'
}
tasks.named<BootJar>("bootJar") {
mainClass.set("com.example.ExampleApplication")
}
Alternatively, the main class name can be configured project-wide using the mainClass
property of the Spring Boot DSL:
-
Groovy
-
Kotlin
springBoot {
mainClass = 'com.example.ExampleApplication'
}
springBoot {
mainClass.set("com.example.ExampleApplication")
}
If the application
plugin has been applied its mainClass
property must be configured and can be used for the same purpose:
-
Groovy
-
Kotlin
application {
mainClass = 'com.example.ExampleApplication'
}
application {
mainClass.set("com.example.ExampleApplication")
}
Lastly, the Start-Class
attribute can be configured on the task’s manifest:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
manifest {
attributes 'Start-Class': 'com.example.ExampleApplication'
}
}
tasks.named<BootJar>("bootJar") {
manifest {
attributes("Start-Class" to "com.example.ExampleApplication")
}
}
If the main class is written in Kotlin, the name of the generated Java class should be used.
By default, this is the name of the Kotlin class with the Kt suffix added.
For example, ExampleApplication becomes ExampleApplicationKt .
If another name is defined using @JvmName then that name should be used.
|
Including Development-only Dependencies
By default all dependencies declared in the developmentOnly
configuration will be excluded from an executable jar or war.
If you want to include dependencies declared in the developmentOnly
configuration in your archive, configure the classpath of its task to include the configuration, as shown in the following example for the bootWar
task:
-
Groovy
-
Kotlin
tasks.named("bootWar") {
classpath configurations.developmentOnly
}
tasks.named<BootWar>("bootWar") {
classpath(configurations["developmentOnly"])
}
Configuring Libraries that Require Unpacking
Most libraries can be used directly when nested in an executable archive, however certain libraries can have problems.
For example, JRuby includes its own nested jar support which assumes that jruby-complete.jar
is always directly available on the file system.
To deal with any problematic libraries, an executable archive can be configured to unpack specific nested jars to a temporary directory when the executable archive is run. Libraries can be identified as requiring unpacking using Ant-style patterns that match against the absolute path of the source jar file:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
requiresUnpack '**/jruby-complete-*.jar'
}
tasks.named<BootJar>("bootJar") {
requiresUnpack("**/jruby-complete-*.jar")
}
For more control a closure can also be used.
The closure is passed a FileTreeElement
and should return a boolean
indicating whether or not unpacking is required.
Making an Archive Fully Executable
Spring Boot provides support for fully executable archives. An archive is made fully executable by prepending a shell script that knows how to launch the application. On Unix-like platforms, this launch script allows the archive to be run directly like any other executable or to be installed as a service.
Currently, some tools do not accept this format so you may not always be able to use this technique.
For example, jar -xf may silently fail to extract a jar or war that has been made fully-executable.
It is recommended that you only enable this option if you intend to execute it directly, rather than running it with java -jar or deploying it to a servlet container.
|
To use this feature, the inclusion of the launch script must be enabled:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
launchScript()
}
tasks.named<BootJar>("bootJar") {
launchScript()
}
This will add Spring Boot’s default launch script to the archive.
The default launch script includes several properties with sensible default values.
The values can be customized using the properties
property:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
launchScript {
properties 'logFilename': 'example-app.log'
}
}
tasks.named<BootJar>("bootJar") {
launchScript {
properties(mapOf("logFilename" to "example-app.log"))
}
}
If the default launch script does not meet your needs, the script
property can be used to provide a custom launch script:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
launchScript {
script = file('src/custom.script')
}
}
tasks.named<BootJar>("bootJar") {
launchScript {
script = file("src/custom.script")
}
}
Using the PropertiesLauncher
To use the PropertiesLauncher
to launch an executable jar or war, configure the task’s manifest to set the Main-Class
attribute:
-
Groovy
-
Kotlin
tasks.named("bootWar") {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.launch.PropertiesLauncher'
}
}
tasks.named<BootWar>("bootWar") {
manifest {
attributes("Main-Class" to "org.springframework.boot.loader.launch.PropertiesLauncher")
}
}
Packaging Layered Jar or War
By default, the bootJar
task builds an archive that contains the application’s classes and dependencies in BOOT-INF/classes
and BOOT-INF/lib
respectively.
Similarly, bootWar
builds an archive that contains the application’s classes in WEB-INF/classes
and dependencies in WEB-INF/lib
and WEB-INF/lib-provided
.
For cases where a docker image needs to be built from the contents of the jar, it’s useful to be able to separate these directories further so that they can be written into distinct layers.
Layered jars use the same layout as regular boot packaged jars, but include an additional meta-data file that describes each layer.
By default, the following layers are defined:
-
dependencies
for any non-project dependency whose version does not containSNAPSHOT
. -
spring-boot-loader
for the jar loader classes. -
snapshot-dependencies
for any non-project dependency whose version containsSNAPSHOT
. -
application
for project dependencies, application classes, and resources.
The layers order is important as it determines how likely previous layers can be cached when part of the application changes.
The default order is dependencies
, spring-boot-loader
, snapshot-dependencies
, application
.
Content that is least likely to change should be added first, followed by layers that are more likely to change.
To disable this feature, you can do so in the following manner:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
layered {
enabled = false
}
}
tasks.named<BootJar>("bootJar") {
layered {
enabled.set(false)
}
}
When a layered jar or war is created, the spring-boot-jarmode-tools
jar will be added as a dependency to your archive.
With this jar on the classpath, you can launch your application in a special mode which allows the bootstrap code to run something entirely different from your application, for example, something that extracts the layers.
If you wish to exclude this dependency, you can do so in the following manner:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
includeTools = false
}
tasks.named<BootJar>("bootJar") {
includeTools.set(false)
}
Custom Layers Configuration
Depending on your application, you may want to tune how layers are created and add new ones.
This can be done using configuration that describes how the jar or war can be separated into layers, and the order of those layers. The following example shows how the default ordering described above can be defined explicitly:
-
Groovy
-
Kotlin
tasks.named("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include "org/springframework/boot/loader/**"
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include "*:*:*SNAPSHOT"
}
intoLayer("dependencies")
}
layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"]
}
}
tasks.named<BootJar>("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include("org/springframework/boot/loader/**")
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include("*:*:*SNAPSHOT")
}
intoLayer("dependencies")
}
layerOrder.set(listOf("dependencies", "spring-boot-loader", "snapshot-dependencies", "application"))
}
}
The layered
DSL is defined using three parts:
-
The
application
closure defines how the application classes and resources should be layered. -
The
dependencies
closure defines how dependencies should be layered. -
The
layerOrder
method defines the order that the layers should be written.
Nested intoLayer
closures are used within application
and dependencies
sections to claim content for a layer.
These closures are evaluated in the order that they are defined, from top to bottom.
Any content not claimed by an earlier intoLayer
closure remains available for subsequent ones to consider.
The intoLayer
closure claims content using nested include
and exclude
calls.
The application
closure uses Ant-style path matching for include/exclude parameters.
The dependencies
section uses group:artifact[:version]
patterns.
It also provides includeProjectDependencies()
and excludeProjectDependencies()
methods that can be used to include or exclude project dependencies.
If no include
call is made, then all content (not claimed by an earlier closure) is considered.
If no exclude
call is made, then no exclusions are applied.
Looking at the dependencies
closure in the example above, we can see that the first intoLayer
will claim all project dependencies for the application
layer.
The next intoLayer
will claim all SNAPSHOT dependencies for the snapshot-dependencies
layer.
The third and final intoLayer
will claim anything left (in this case, any dependency that is not a project dependency or a SNAPSHOT) for the dependencies
layer.
The application
closure has similar rules.
First claiming org/springframework/boot/loader/**
content for the spring-boot-loader
layer.
Then claiming any remaining classes and resources for the application
layer.
The order that intoLayer closures are added is often different from the order that the layers are written.
For this reason the layerOrder method must always be called and must cover all layers referenced by the intoLayer calls.
|