'How do I create an executable fat JAR with Gradle with implementation dependencies?
I've got a simple project in Gradle 4.6 and would like to make an executable JAR of it. I've tried shadow
, gradle-fatjar-plugin
, gradle-one-jar
, spring-boot-gradle-plugin
plugins but neither of them adds my dependencies declared as implementation
(I don't have any compile
ones). It works with compile
e.g. for gradle-one-jar
plugin but I would like to have implementation
dependencies.
Solution 1:[1]
You can use the following code.
jar {
manifest {
attributes(
'Main-Class': 'com.package.YourClass'
)
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Be sure to replace com.package.YourClass
with the fully qualified class name containing static void main( String args[] )
.
This will pack the runtime dependencies. Check the docs if you need more info.
Solution 2:[2]
Based on the accepted answer, I needed to add one more line of code:
task fatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'com.yourpackage.Main'
}
archiveClassifier = "all"
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}
Without this additional line, it omitted my source files and only added the dependencies:
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
For newer gradle (7+), you may see this error:
Execution failed for task ':fatJar'.
> Entry [some entry here] is a duplicate but no duplicate handling strategy has been set. Please
refer to https://docs.gradle.org/7.1/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy
for details.
If this happens add a duplicatesStrategy
such as duplicatesStrategy "exclude"
to the fatJar
task.
And likewise, for Gradle 7+, you have to just remove the configuration.compile.collect
line because it is no longer a valid configuration in this version of gradle.
Solution 3:[3]
The same task can be achieved using Gradle Kotlin DSL in a similar way:
val jar by tasks.getting(Jar::class) {
manifest {
attributes["Main-Class"] = "com.package.YourClass"
}
from(configurations
.runtime
// .get() // uncomment this on Gradle 6+
// .files
.map { if (it.isDirectory) it else zipTree(it) })
}
Solution 4:[4]
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
This line is essential to me.
Solution 5:[5]
previous answers are a little outdated nowadays, see here for something working with gradle-7.4: How to create a fat JAR with Gradle Kotlin script?
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
val dependencies = configurations
.runtimeClasspath
.get()
.map(::zipTree) // OR .map { zipTree(it) }
from(dependencies)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
Solution 6:[6]
Here I provide solutions for Kotlin DSL (build.gradle.kts).
Note that the first 3 methods modify the existing Jar
task of Gradle.
Method 1: Placing library files beside the result JAR
This method does not need application
or any other plugins.
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
manifest.attributes["Class-Path"] = configurations
.runtimeClasspath
.get()
.joinToString(separator = " ") { file ->
"libs/${file.name}"
}
}
Note that Java requires us to use relative URLs for the Class-Path
attribute. So, we cannot use the absolute path of Gradle dependencies (which is also prone to being changed and not available on other systems). If you want to use absolute paths, maybe this workaround will work.
Create the JAR with the following command:
./gradlew jar
The result JAR will be created in build/libs/ directory by default.
After creating your JAR, copy your library JARs in libs/ sub-directory of where you put your result JAR. Make sure your library JAR files do not contain space in their file name (their file name should match the one specified by ${file.name}
variable above in the task).
Method 2: Embedding the libraries in the result JAR (fat or uber JAR)
This method too does not need any Gradle plugin.
tasks.jar {
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
val dependencies = configurations
.runtimeClasspath
.get()
.map(::zipTree) // OR .map { zipTree(it) }
from(dependencies)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
Creating the JAR is exactly the same as the previous method.
Method 3: Using the Shadow plugin (to create a fat or uber JAR)
plugins {
id("com.github.johnrengelman.shadow") version "6.0.0"
}
// Shadow task depends on Jar task, so these configs are reflected for Shadow as well
tasks.jar {
manifest.attributes["Main-Class"] = "org.example.MainKt"
}
Create the JAR with this command:
./gradlew shadowJar
See Shadow documentations for more information about configuring the plugin.
Method 4: Creating a new task (instead of modifying the Jar
task)
tasks.create("MyFatJar", Jar::class) {
group = "my tasks" // OR, for example, "build"
description = "Creates a self-contained fat JAR of the application that can be run."
manifest.attributes["Main-Class"] = "com.example.MyMainClass"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
val dependencies = configurations
.runtimeClasspath
.get()
.map(::zipTree)
from(dependencies)
with(tasks.jar.get())
}
Running the created JAR
java -jar my-artifact.jar
The above solutions were tested with:
- Java 17
- Gradle 7.1 (which uses Kotlin 1.4.31 for .kts build scripts)
See the official Gradle documentation for creating uber (fat) JARs.
For more information about manifests, see Oracle Java Documentation: Working with Manifest files.
For difference between tasks.create()
and tasks.register()
see this post.
Note that your resource files will be included in the JAR file automatically (assuming they were placed in /src/main/resources/ directory or any custom directory set as resources root in the build file). To access a resource file in your application, use this code (note the /
at the start of names):
- Kotlin
val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText() // Alternative ways: // val vegetables = object{}.javaClass.getResource("/vegetables.txt").readText() // val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText() // val vegetables = object{}.javaClass.getResourceAsStream("/vegetables.txt").reader().readText()
- Java
var stream = MyClass.class.getResource("/vegetables.txt").openStream(); // OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt"); var reader = new BufferedReader(new InputStreamReader(stream)); var vegetables = reader.lines().collect(Collectors.joining("\n"));
Solution 7:[7]
Kotlin 1.3.72 & JVM plugin, Gradle 6.5.1
Syntax is changing quickly in all these platforms
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
val main = sourceSets.main.get()
//TODO
register<Jar>("buildFatJar") {
group = "app-backend"
dependsOn(build)
// shouldRunAfter(parent!!.tasks["prepCopyJsBundleToKtor"]) -> This is for incorporating KotlinJS gradle subproject resulting js file.
manifest {
attributes["Main-Class"] = "com.app.app.BackendAppKt"
}
from(configurations.compileClasspath.get().files.map { if (it.isDirectory) it else zipTree(it) })
with(jar.get() as CopySpec)
archiveBaseName.set("${project.name}-fat")
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Dave Jarvis |
Solution 2 | Nicholas DiPiazza |
Solution 3 | user2297550 |
Solution 4 | LiuWenbin_NO. |
Solution 5 | soloturn |
Solution 6 | |
Solution 7 | gunslingor |