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:

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:

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:

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:

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

This line is essential to me.

Solution 5:

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")
    }
}