How to create a fat JAR with Gradle Kotlin script?
Solution 1
Here is a version that does not use a plugin, more like the Groovy version.
import org.gradle.jvm.tasks.Jar
val fatJar = task("fatJar", type = Jar::class) {
baseName = "${project.name}-fat"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = version
attributes["Main-Class"] = "com.mkyong.DateUtils"
}
from(configurations.runtime.map({ if (it.isDirectory) it else zipTree(it) }))
with(tasks["jar"] as CopySpec)
}
tasks {
"build" {
dependsOn(fatJar)
}
}
Some commenters pointed out that this does not work anymore with newer Gradle versions. Update tested with Gradle 5.4.1:
import org.gradle.jvm.tasks.Jar
val fatJar = task("fatJar", type = Jar::class) {
baseName = "${project.name}-fat"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = version
attributes["Main-Class"] = "com.mkyong.DateUtils"
}
from(configurations.runtimeClasspath.get().map({ if (it.isDirectory) it else zipTree(it) }))
with(tasks.jar.get() as CopySpec)
}
tasks {
"build" {
dependsOn(fatJar)
}
}
Note the difference in configurations.runtimeClasspath.get()
and with(tasks.jar.get() as CopySpec)
.
Solution 2
Here is how to do it as of Gradle 6.5.1, Kotlin/Kotlin-Multiplatform 1.3.72, utilizing a build.gradle.kts file and without using an extra plugin which does seem unnecessary and problematic with multiplatform;
Note: in reality, few plugins work well with the multiplatform plugin from what I can tell, which is why I suspect its design philosophy is so verbose itself. It's actually fairly elegant IMHO, but not flexible or documented enough so it takes a ton of trial and error to setup even WITHOUT additional plugins.
Hope this helps others.
kotlin {
jvm {
compilations {
val main = getByName("main")
tasks {
register<Jar>("fatJar") {
group = "application"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = archiveVersion
attributes["Main-Class"] = "[[mainClassPath]]"
}
archiveBaseName.set("${project.name}-fat")
from(main.output.classesDirs, main.compileDependencyFiles)
with(jar.get() as CopySpec)
}
}
}
}
}
Solution 3
Note that the first 3 methods here 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 file (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 4
You could use the ShadowJar plugin to build a fat jar:
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
buildscript {
repositories {
mavenCentral()
gradleScriptKotlin()
}
dependencies {
classpath(kotlinModule("gradle-plugin"))
classpath("com.github.jengelman.gradle.plugins:shadow:1.2.3")
}
}
apply {
plugin("kotlin")
plugin("com.github.johnrengelman.shadow")
}
repositories {
mavenCentral()
}
val shadowJar: ShadowJar by tasks
shadowJar.apply {
manifest.attributes.apply {
put("Implementation-Title", "Gradle Jar File Example")
put("Implementation-Version" version)
put("Main-Class", "com.mkyong.DateUtils")
}
baseName = project.name + "-all"
}
Simply run the task with 'shadowJar'.
NOTE: This assumes you're using GSK 0.7.0 (latest as of 02/13/2017).
elect
https://github.com/elect86/ https://github.com/kotlin-graphics
Updated on July 08, 2022Comments
-
elect almost 2 years
As titled, I'd like to know how to modify the
gradle.build.kts
in order to have a task to create a uniquejar
with all the dependencies (kotlin lib included) inside.I found this sample in Groovy:
//create a single Jar with all dependencies task fatJar(type: Jar) { manifest { attributes 'Implementation-Title': 'Gradle Jar File Example', 'Implementation-Version': version, 'Main-Class': 'com.mkyong.DateUtils' } baseName = project.name + '-all' from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } with jar }
But I have no idea how I could write that in kotlin, other than:
task("fatJar") { }
-
Mahozad about 2 yearsHere is a related question.
-
Mahozad about 2 yearsDoes this answer your question? Building a self-executable jar with Gradle and Kotlin
-
-
elect over 7 yearsNot working, in the first line
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
,ShadowJar
is unresolved.. -
mbStavola about 7 yearsHave you tried my answer since? I have it tested and working. I think the original problem was that the() required an extension class.
-
elect about 7 yearsI tried it again right now, on the
tasks
it says[DELEGATE_SPECIAL_FUNCTION_MISSING] Missing 'getValue(Build_gradle, KProperty<*>)' method on delegate of type 'TaskContainer!'
-
mbStavola about 7 yearsHm, are you on the latest GSK for sure? Compiler and plugin too?
-
elect about 7 years
-
mbStavola about 7 yearsI took a look and saw that you weren't on the newest dev version of GSK, but rather the most recent stable version. To take advantage of task delegation as outlined above, you'll need to update your gradle distributionUrl. Once I cloned your repo and updated that, it all worked. You can find the latest releases for GSK here: github.com/gradle/gradle-script-kotlin/releases
-
elect about 7 yearsJust tried once again, GSK 0.8.0, same result
-
Mitchell Tracy over 6 yearsHe's not kidding. This should be added somewhere in github.com/gradle/kotlin-dsl/tree/master/samples
-
PHPirate over 5 yearsNote that in Gradle 5 you will have to replace
configurations.runtime.map
withconfigurations.runtime.get().map
to avoidunresolved reference: isDirectory
. See discussion here. -
Laurence about 5 yearsAnd also note that for Gradle 5.4 you will need to replace
with(tasks["jar"] as CopySpec)
withwith(getByName("jar") as CopySpec)
-
t-h- about 5 years@Laurence using Gradle 5.4 your suggested modification actually broke it for me. I got an error:
Cause: null cannot be cast to non-null type org.gradle.api.file.CopySpec
-
jtonic almost 5 yearsIn gradle 5.4.1 I had to replace
with(tasks["jar"] as CopySpec)
withval jar: CopySpec by getting(Jar::class); with(jar)
-
mbonnin almost 5 yearsI also had to
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/INDEX.LIST")
for the main class to be found in the jar -
breandan over 4 yearsHave to use
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
inside thefatJar
task or exclude some files to avoid failure -LICENSE.txt
inside eachjar
dependency was getting included, which caused some errors. -
breandan over 4 yearsMake sure that you also exclude
**.kotlin_metadata
if you want others to consume the fat JAR and receive IDE support. Otherwise the IDE will be unable to resolve references inside the fat JAR: youtrack.jetbrains.com/issue/KT-25709 -
mustofa.id about 4 yearsFor who facing deprecated warning on
baseName
andversion
, usearchiveBaseName.set("")
andarchiveVersion
instead. -
Chris about 3 yearsThis does not work anymore for gradle 6.8
-
FrontierPsychiatrist about 3 years@Chris Try it with
attributes["Implementation-Version"] = archiveVersion
instead -
Dave Yarwood over 2 yearsAs of Gradle 7.3.3, the
from(configurations...)
line being present results in this error:Cannot change dependencies of dependency configuration ':jvmApi' after it has been included in dependency resolution.
-
Dave Yarwood over 2 years...Although my problem might be that I'm trying to do this in a multiplatform project, and it looks like I might have the task defined in the wrong place (at the top level at the bottom of the file, instead of within the
kotlin { jvm { ... } }
block. -
soloturn about 2 yearshow would you do this for variants, if one wants a slim jar, and, opitonally a fat(ter) jar? am trying for plantuml, and it is either fat or slim - but no option to control it.
-
Mahozad about 2 yearsSorry, I don't know about this.
-
soloturn about 2 yearsit works fine like you posted, with the dedicated task, method 4. only thing i am wondering - how to sign the fat jar then, as the standard sign task would not find it.
-
Mahozad about 2 yearsMaybe this Gradle guide can help you.