Using Gradle to build a jar with dependencies

GradleUberjar

Gradle Problem Overview


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.

Gradle Solutions


Solution 1 - Gradle

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

Solution 2 - Gradle

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

Solution 3 - Gradle

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.

Solution 4 - Gradle

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

Solution 5 - Gradle

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

Solution 6 - Gradle

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

Solution 7 - Gradle

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'
}

Solution 8 - Gradle

Simple sulution

jar {
    manifest {
        attributes 'Main-Class': 'cova2.Main'
    } 
    doFirst {
        from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}

Solution 9 - Gradle

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

Solution 10 - Gradle

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.

Solution 11 - Gradle

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

Solution 12 - Gradle

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 {
      -----
    }
}

Solution 13 - Gradle

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

Solution 14 - Gradle

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
}

Solution 15 - Gradle

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

Solution 16 - Gradle

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.

Solution 17 - Gradle

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

Solution 18 - Gradle

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBen McCannView Question on Stackoverflow
Solution 1 - GradleBen McCannView Answer on Stackoverflow
Solution 2 - GradleblootsvoetsView Answer on Stackoverflow
Solution 3 - GradleFelixView Answer on Stackoverflow
Solution 4 - GradleLanDenLabsView Answer on Stackoverflow
Solution 5 - GradleAronView Answer on Stackoverflow
Solution 6 - GradleAlgorithmView Answer on Stackoverflow
Solution 7 - GradleItalo BorssattoView Answer on Stackoverflow
Solution 8 - GradleJonas MayerView Answer on Stackoverflow
Solution 9 - GradleSalomon KabongoView Answer on Stackoverflow
Solution 10 - GradleMiguelSlvView Answer on Stackoverflow
Solution 11 - GradleNickUnuchekView Answer on Stackoverflow
Solution 12 - Gradlesujith kasthooriView Answer on Stackoverflow
Solution 13 - GradleTraycho IvanovView Answer on Stackoverflow
Solution 14 - GradleHereAndBeyondView Answer on Stackoverflow
Solution 15 - GradleAlex UrecheView Answer on Stackoverflow
Solution 16 - GradleJose SolorzanoView Answer on Stackoverflow
Solution 17 - GradleRyan Luo XuView Answer on Stackoverflow
Solution 18 - GradlemigView Answer on Stackoverflow