79

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.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
Dmitry Senkovich
  • 5,521
  • 8
  • 37
  • 74
  • Does this answer your question? [Creating runnable JAR with Gradle](https://stackoverflow.com/questions/21721119/creating-runnable-jar-with-gradle) – Mahozad Feb 18 '22 at 17:10

8 Answers8

125

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.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
miskender
  • 7,460
  • 1
  • 19
  • 23
  • 1
    whats the name of the fat jar and in which dir do I find it? – Vijay Kumar Jun 18 '18 at 20:15
  • 1
    @VijayKumar Mine is under `build/libs`. – Shayan Toqraee Oct 09 '18 at 23:31
  • 7
    You may want to use `configurations.compileClasspath.filter{ it.exists() }.collect { it.isDirectory() ? it : zipTree(it) }`. That way if you have non-existant classpath entries it won't get hung up. I'm using Kotlin so the java source classpath entry didn't have anything and it got pretty cranky. – Alex Ives Oct 16 '18 at 18:16
  • When I use this code, all of the library classes are included in the uberjar, but the classes in the source directory aren't. – user64141 Dec 17 '19 at 23:27
  • @user64141 Your source files has to be in the directory src/main/java next to build.gradle file, that you added this code. (which is the standard convention) If they are in another folder they wont be included in the jar. I suggest using standard folder structure. This is the only case that I can think that cause your described problem – miskender Dec 18 '19 at 05:13
  • Why isn't this included when a Gradle project is created in the IDE? – xdevs23 Aug 08 '20 at 13:53
  • What exactly does this line do? The from { configurations ...} – joshpetit Oct 18 '20 at 12:28
  • does not work anymore in newer gradle version. try this answer: https://stackoverflow.com/a/61198352/393657 – stryba Oct 26 '20 at 09:55
  • Note to self: this worked for me with JDK 11, Gradle 6.4 – Sridhar Sarnobat Oct 29 '20 at 21:03
  • Thanks! I was using `into('lib') { from configurations.runtime }` in gradle 2.8 so just had to update it to `into('lib') { from configurations.runtimeClasspath }` in gradle 6.7.1. – Shadow Man Jan 04 '21 at 17:59
  • This post works in Gradle 6.8.2. Thank you – mending3 Feb 10 '21 at 04:18
  • 1
    Man, you saved me a lot of time. There was a howto on the net but it was buggy :) – Gabor Somogyi Aug 09 '21 at 08:17
  • See [Gradle "Entry .classpath is a duplicate but no duplicate handling strategy has been set"](https://stackoverflow.com/a/68922734/432903) for duplicate strategy – prayagupa Apr 27 '23 at 23:01
  • Note it's `attributes(` with a ROUND bracket, not curly brace :D – Sridhar Sarnobat Jun 27 '23 at 23:27
30

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.

Nicholas DiPiazza
  • 10,029
  • 11
  • 83
  • 152
JohnP2
  • 1,899
  • 19
  • 17
  • This worked where all the others didn't for Gradle 6.0.1. Mysteriously, the second line didn't seem to be required in Gradle 4.x, but my old build config suddenly stopped creating fat JARs once upgraded to Gradle 6 or 5. – Kristopher Noronha May 26 '20 at 19:21
  • 4
    Gradle 6.5 reports the following warning: The compile configuration has been deprecated for resolution. This will fail with an error in Gradle 7.0. Please resolve the compileClasspath configuration instead. Replacing "compile" by "compileClasspath" fixed it. – ocroquette Jul 06 '20 at 22:09
  • Worked perfectly, not sure how you found this but you are doing gods work!! – j bel Mar 31 '21 at 08:08
  • @jbel Thanks! I stumbled upon it by combining info from another post – JohnP2 Mar 31 '21 at 17:03
  • Bit of a weird one for me, trying to run a github project (which had been set up with a gradle wrapper) and kept getting "classdefnotfoundexception" errors for dependencies. I checked the gradle.build file in the submodule and found dependencies were being imported using the "implementation" keyword (rather than "compile"). Once I added the "configuration.runtimeClasspath" line for the jar task issues were resolved. – C Murphy Oct 07 '21 at 12:01
15

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) })
}
user2297550
  • 3,142
  • 3
  • 28
  • 39
SerCe
  • 5,826
  • 2
  • 32
  • 53
7

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
}
soloturn
  • 958
  • 9
  • 8
5

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"));
    
Mahozad
  • 18,032
  • 13
  • 118
  • 133
  • I wish I could get method 1) to work on a newer version of gradle. It is nice to retain the helper jars without flattening everything. – Sridhar Sarnobat Sep 06 '22 at 01:38
4

from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }

This line is essential to me.

LiuWenbin_NO.
  • 1,216
  • 1
  • 16
  • 25
1

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")
    }
}
gunslingor
  • 1,358
  • 12
  • 34
1
mainClassName = 'Main'
sourceSets {
    main {
        java {
            srcDirs 'src/main/java', 'src/main/resources'
        }
    }
}
jar{
    manifest {
        attributes(
                "Main-Class": "$mainClassName",

        )

    }
    from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) 
         }
    }
    exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    dependsOn ('dependencies')
}
garawaa garawaa
  • 154
  • 1
  • 6