165

I have a multiproject build and I put a task to build a fat JAR in one of the subprojects. I created the task similar to the one described in this cookbook.

jar {
  from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  manifest { attributes 'Main-Class': 'com.benmccann.gradle.test.WebServer' }
}

Running it results in the following error:

Cause: You can't change a configuration which is not in unresolved state!

I'm not sure what this error means. I also reported this on the Gradle JIRA in case it is a bug.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
Ben McCann
  • 18,548
  • 25
  • 83
  • 101
  • 1
    For a complete answer for Kotlin DSL (build.gradle.kts) see [this post](https://stackoverflow.com/a/70864141/8583692). – Mahozad Feb 09 '22 at 17:39
  • 1
    Does this answer your question? [Creating runnable JAR with Gradle](https://stackoverflow.com/questions/21721119/creating-runnable-jar-with-gradle) – Mahozad Feb 09 '22 at 17:42
  • https://www.baeldung.com/gradle-fat-jar – k4dima Apr 26 '23 at 10:41

19 Answers19

253

I posted a solution in JIRA against Gradle:

// Include dependent libraries in archive.
mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  

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

Note that mainClassName must appear BEFORE jar {.

yankee
  • 38,872
  • 15
  • 103
  • 162
Ben McCann
  • 18,548
  • 25
  • 83
  • 101
  • 5
    I had to modify this to configurations.runtime.collect for my project as I have runtime dependencies as well. – vextorspace Jun 29 '16 at 17:32
  • 9
    I had to add `def mainClassName` to make the code work... I was receiving Could not set unknown property 'mainClassName' for root project – hanskoff May 12 '17 at 12:45
  • 3
    How do you handle file name collisions? Files on the same path in different JARs will be overwritten. – wst Aug 20 '17 at 10:47
  • @waste there is a DuplicatesStrategy in all Copy tasks (which includes Jar / Zip). `duplicatesStategy='EXCLUDE'` will simply ignore anything that already exists. I recommend using the `-all` distribution of gradle with sources so you can easily drill into task classes and read their excellent documentation. – Ajax Mar 01 '18 at 02:41
  • @Ajax But the thing is, that you don't want to ignore those files. They are there for a reason and it may happen that file with the same name would have different meaning for different lib. I've already burned my hands with such setup. If you'd ask me - building a fat jar is never good idea. – wst Mar 15 '18 at 03:03
  • So what's the difference between the code in the answer and the code in the question (except indentation)? – AlikElzin-kilaka Oct 28 '18 at 15:56
  • 11
    Unfortunately this does not work any more. I use gradle 4.10 and the new `implementation` configuration instead of the now deprecated `compile`. The above code builds me a small jar without the dependencies. When I change it ( `from { configurations.implementation.collect {...} }`), an error occurs saying that resolving configuration 'implementation' directly is not allowed – Bastian Voigt Mar 08 '19 at 10:11
  • is causing stackoverflowexception – Dr Deo Jul 29 '19 at 16:37
  • 6
    @BastianVoigt `configurations.compileClasspath` will fix all the `implementation`s, but will leave out the `api` dependencies afik. Found here in another answer the solution `runtimeClasspath`. That includes the `api` dependencies too. – rekire Jan 14 '20 at 07:48
  • Can you please provide this example in kotlin in addtion to groovy? I have hard time converting that. Thanks! – Alkis Mavridis Oct 08 '20 at 15:38
  • 1
    Looks like the first one doesn't work on Gradle 6, unless I'm doing something wrong. `Could not set unknown property 'mainClassName'` – Sridhar Sarnobat Oct 29 '20 at 20:59
  • In Gradle 6.x instead of declaring 'mainClassName' variable use main class name directly as an attribute value: `attributes "Main-Class": "com.company.application.Main"` – RealMan May 17 '21 at 08:28
  • What i don't like about this solution that all content is exploded in the jar, we get classes/folders/content everywhere. The OneJar solution suggested by @Italo Borssatto is a much neater final jar with all contained jars in a lib folder. – Michael Jan 30 '23 at 16:03
81

The answer by @felix almost brought me there. I had two issues:

  1. With Gradle 1.5, the manifest tag was not recognised inside the fatJar task, so the Main-Class attribute could not directly be set
  2. the jar had conflicting external META-INF files.

The following setup resolves this

jar {
  manifest {
    attributes(
      'Main-Class': 'my.project.main',
    )
  }
}

task fatJar(type: Jar) {
  manifest.from jar.manifest
  classifier = 'all'
  from {
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
  } {
    exclude "META-INF/*.SF"
    exclude "META-INF/*.DSA"
    exclude "META-INF/*.RSA"
  }
  with jar
}

To add this to the standard assemble or build task, add:

artifacts {
    archives fatJar
}

Edit: thanks to @mjaggard: in recent versions of Gradle, change configurations.runtime to configurations.runtimeClasspath

blootsvoets
  • 1,277
  • 11
  • 11
  • 3
    This also fixed a problem I had where one of my dependency jars was signed. The signature files were put into my jar's META-INF, but the signature no longer matched the content. – Flavin Mar 07 '16 at 17:08
  • 2
    Special thanks for `artifacts`: exactly what I was looking for. – AlexR Mar 28 '17 at 11:26
  • When you run `gradle fatJar` the runtime dependencies don't seem to be compiled, so they cannot be copied. – mjaggard Jan 04 '18 at 10:44
  • The best answer! – Giri Apr 07 '22 at 01:31
66

If you want the jar task to behave normally and also have an additional fatJar task, use the following:

task fatJar(type: Jar) {
    classifier = 'all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

The important part is with jar. Without it, the classes of this project are not included.

kellyfj
  • 6,586
  • 12
  • 45
  • 66
Felix
  • 5,804
  • 4
  • 25
  • 37
  • 1
    Also see the following issue if you are using signed jars to included and run into a problem with signatures: http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar – Peter N. Steinmetz Apr 04 '14 at 23:56
  • This works well, except that if I try to modify the manifest file, I get two manifest files. – Sundae Jul 03 '14 at 09:23
  • Worth noting (as I included in my write-up [here](http://stackoverflow.com/a/25095068/1040915)) that, if you only want the fatJar task, you can simply set `from files({configurations...zipTree(it)}}, sourceSets.main.java)` - or, as appropriate. – scubbo Aug 02 '14 at 12:33
  • 6
    This does not work. The Manifest file is empty with this solution. – Jonas Oct 28 '15 at 20:30
  • 4
    My 2 cents: It is better to set a classifier than to change the name. Put classifier = 'all' instead of baseName = project.name + '-all'. That way you keep the artifact name in compliance with Maven/Nexus policies. – taciosd Apr 30 '16 at 21:46
  • 1
    Add ```group "build"``` and this task will be in ```build``` group (with other tasks, i.e. ```jar``` task. – MAGx2 Nov 20 '16 at 17:28
  • 4
    I can't find any kind of documentation on the `with jar` keyword, what exactly does it do? – Philipp Hemmelmayr Jan 30 '19 at 09:10
  • @PhilippHemmelmayr I found it here https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/AbstractCopyTask.html#with-org.gradle.api.file.CopySpec...- and here https://docs.gradle.org/current/javadoc/org/gradle/api/file/CopySpec.html. Basically it reuses the from-filter-into stuff. – Petr Újezdský Mar 23 '22 at 13:44
21

Since use of compile to list dependencies is now deprecated and all should switch to implementation the solution to build a Jar with all dependencies should use the example from this website.

https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example

Specifically this command:

configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it)

Here is full gradle section: [1]: https://docs.gradle.org/current/userguide/working_with_files.html#sec:creating_uber_jar_example

task uberJar(type: Jar) {
archiveClassifier = 'uber'

from sourceSets.main.output

dependsOn configurations.runtimeClasspath
from {
    configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}}
LanDenLabs
  • 1,566
  • 16
  • 10
10

This works fine for me.

My Main class:

package com.curso.online.gradle;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

public class Main {

    public static void main(String[] args) {
        Logger logger = Logger.getLogger(Main.class);
        logger.debug("Starting demo");

        String s = "Some Value";

        if (!StringUtils.isEmpty(s)) {
            System.out.println("Welcome ");
        }

        logger.debug("End of demo");
    }

}

And it is the content of my file build.gradle:

apply plugin: 'java'

apply plugin: 'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
    compile  'org.apache.commons:commons-lang3:3.0'
    compile  'log4j:log4j:1.2.16'
}

task fatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'com.curso.online.gradle.Main'
    }
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

And I write the following in my console:

java -jar ProyectoEclipseTest-all.jar

And the output is great:

log4j:WARN No appenders could be found for logger (com.curso.online.gradle.Main)
.
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more in
fo.
Welcome
Bombe
  • 81,643
  • 20
  • 123
  • 127
Aron
  • 1,142
  • 1
  • 14
  • 26
8

The answer from @ben almost works for me except that my dependencies are too big and I got the following error

Execution failed for task ':jar'.
> archive contains more than 65535 entries.

  To build this archive, please enable the zip64 extension.

To fix this problem, I have to use the following code

mainClassName = "com.company.application.Main"

jar {
  manifest { 
    attributes "Main-Class": "$mainClassName"
  }  
  zip64 = true
  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
  }
}
Algorithm
  • 81
  • 1
  • 4
7

To generate a fat JAR with a main executable class, avoiding problems with signed JARs, I suggest gradle-one-jar plugin. A simple plugin that uses the One-JAR project.

Easy to use:

apply plugin: 'gradle-one-jar'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.github.rholder:gradle-one-jar:1.0.4'
    }
}

task myjar(type: OneJar) {
    mainClass = 'com.benmccann.gradle.test.WebServer'
}
Italo Borssatto
  • 15,044
  • 7
  • 62
  • 88
6

Simple sulution

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}
Jonas Mayer
  • 73
  • 2
  • 3
6

Based on the proposed solution by @blootsvoets, I edited my jar target this way :

jar {
    manifest {
        attributes('Main-Class': 'eu.tib.sre.Main')
    }
    // Include the classpath from the dependencies 
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    // This help solve the issue with jar lunch
    {
    exclude "META-INF/*.SF"
    exclude "META-INF/*.DSA"
    exclude "META-INF/*.RSA"
  }
}
Salomon Kabongo
  • 549
  • 5
  • 15
3

I use next script for Gradle 7.3.3. It resolves errors and exceptions that I was faced with when I was trying to implement solutions from this question.

jar {
    manifest {
        attributes(
                "Main-Class": "path.to.main.Application",
        )
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
HereAndBeyond
  • 794
  • 1
  • 8
  • 17
2

There is gradle plugin shadow jar with seamless setup.

plugins {
    id "com.github.johnrengelman.shadow" version "5.0.0"
}

shadowJar {
    mergeServiceFiles()
}

Please check about version compatibilities with your gradle version here: https://github.com/johnrengelman/shadow#latest-test-compatibility

Traycho Ivanov
  • 2,887
  • 14
  • 24
2

This is for Kotlin DSL (build.gradle.kts).

Method 1 (no need for application or other plugins)

tasks.jar {
    manifest.attributes["Main-Class"] = "com.example.MyMainClass"
    // OR another notation
    // manifest {
    //     attributes["Main-Class"] = "com.example.MyMainClass"
    // }
}

If you use any external libraries, use below code. Copy 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.

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.

Method 2: Embedding libraries (if any) in the result JAR (fat or uber JAR)

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 will be 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.

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.

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
1

For those who need to build more than one jar from the project.

Create a function in gradle:

void jarFactory(Jar jarTask, jarName, mainClass) {
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + + ' started'
    }

    jarTask.manifest {
        attributes(
                'Main-Class':  mainClass
        )
    }
    jarTask.classifier = 'all'
    jarTask.baseName = jarName
    jarTask.from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }
    jarTask.with jar 
    jarTask.doFirst {
        println 'Build jar ' + jarTask.name + ' ended'
    }
}

then call:

task makeMyJar(type: Jar) {
    jarFactory(it, 'MyJar', 'org.company.MainClass')
}

Works on gradle 5.

Jar will be placed at ./build/libs.

MiguelSlv
  • 14,067
  • 15
  • 102
  • 169
1

I use task shadowJar by plugin . com.github.jengelman.gradle.plugins:shadow:5.2.0

Usage just run ./gradlew app::shadowJar result file will be at MyProject/app/build/libs/shadow.jar

top level build.gradle file :

 apply plugin: 'kotlin'

buildscript {
    ext.kotlin_version = '1.3.61'

    repositories {
        mavenLocal()
        mavenCentral()
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
    }
}

app module level build.gradle file

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'

sourceCompatibility = 1.8

kapt {
    generateStubs = true
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"
    shadow "org.seleniumhq.selenium:selenium-java:4.0.0-alpha-4"

    implementation project(":module_remote")
    shadow project(":module_remote")
}

jar {
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
    manifest {
        attributes(
                'Main-Class': 'com.github.kolyall.TheApplication',
                'Class-Path': configurations.compile.files.collect { "lib/$it.name" }.join(' ')
        )
    }
}

shadowJar {
    baseName = 'shadow'
    classifier = ''
    archiveVersion = ''
    mainClassName = 'com.github.kolyall.TheApplication'

    mergeServiceFiles()
}

NickUnuchek
  • 11,794
  • 12
  • 98
  • 138
1

Excluding unwanted Manifest entries fixed the MainClass file not found error in a Gradle build jar file.

jar{
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
    from {
      -----
    }
}
0

Gradle 6.3, Java library. The code from "jar task" adds the dependencies to the "build/libs/xyz.jar" when running "gradle build" task.

plugins {
    id 'java-library'
}

jar {
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
Alex Ureche
  • 351
  • 2
  • 7
0

There's something to keep in mind about this type of solution:

task fatJar(type: Jar) {
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

It works so long as you're using "compile" dependencies. It doesn't work if you're using "implementation" dependencies.

Jose Solorzano
  • 393
  • 4
  • 6
0

Try "runtimeClasspath" if "compile" and "implementation" not working.

jar {
    manifest {
        attributes "Main-Class": "com.example.app"
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
Ryan Luo Xu
  • 119
  • 5
-1

If you're used to ant then you could try the same with Gradle too:

task bundlemyjava{
    ant.jar(destfile: "build/cookmyjar.jar"){
        fileset(dir:"path to your source", includes:'**/*.class,*.class', excludes:'if any')
        } 
}
Fred
  • 3,324
  • 1
  • 19
  • 29
mig
  • 41
  • 3