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 contain SNAPSHOT.

  • spring-boot-loader for the jar loader classes.

  • snapshot-dependencies for any non-project dependency whose version contains SNAPSHOT.

  • 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.