155

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 the 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.

Yves M.
  • 28,433
  • 22
  • 100
  • 135
Ben McCann
  • 17,641
  • 23
  • 79
  • 99
  • 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
  • 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

18 Answers18

243

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
  • 36,174
  • 13
  • 96
  • 156
Ben McCann
  • 17,641
  • 23
  • 79
  • 99
  • 4
    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
  • 5
    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
  • 2
    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
  • 8
    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
  • 4
    @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
77

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,177
  • 9
  • 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
64

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
  • 5,985
  • 8
  • 42
  • 64
Felix
  • 5,464
  • 4
  • 24
  • 36
  • 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
14

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,378
  • 15
  • 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
  • 78,266
  • 20
  • 120
  • 125
Aron
  • 1,102
  • 1
  • 14
  • 24
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
6

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
  • 13,777
  • 7
  • 62
  • 83
5

Simple sulution

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

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
  • 499
  • 3
  • 12
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
  • 11,445
  • 12
  • 88
  • 150
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
  • 10,046
  • 9
  • 87
  • 127
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 {
      -----
    }
}
1

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,242
  • 10
  • 22
1

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
  • 203
  • 4
  • 11
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
  • 183
  • 1
  • 6
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
  • 333
  • 3
  • 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
  • 18
  • 29
mig
  • 41
  • 3