Skip to content

How do I create a jar with all dependencies using Gradle 4.4?

This question is related to this one — however, due to the deprecation of compile in favor of implementation, it doesn’t work. It does pick up dependencies that are declared with compile. However, with it being deprecated, using it isn’t an option (and we’d be right back here when it’s removed anyway)


I got this Gradle task:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from { 
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

And there’s just one dependency, looking aside test dependencies:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    testImplementation group: 'junit', name: 'junit', version: '4.12'
}

Running from the IDE works fine. However, when I deploy to my Raspberry Pi (or use the jar gradlew fatJar results in locally), I get this exception:

$ java -jar java-sense-hat-1.0a.jar
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
    at io.github.lunarwatcher.pi.sensehat.UtilsKt.getSenseHat(Utils.kt:18)
    at io.github.lunarwatcher.pi.sensehat.SenseHat.<init>(SenseHat.java:12)
    at io.github.lunarwatcher.pi.sensehat.Tests.main(Tests.java:9)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
    at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
    ... 3 more

Which is triggered by this line:

return Optional.empty()

in a Kotlin method returning an Optional<File>. Answer on related question:

Either kotlin-runtime has to be in classpath and verify with $ echo $CLASSPATH.

Or you have to add kotlin-runtime to maven and then assemble inside the jar itself with mvn compile assembly:single,

Which means the kotlin-runtime isn’t included in the classpath. Before you go ahead and answer “add kotlin-runtime to your dependencies”, it’s a part of the stdlib:

Kotlin Runtime (deprecated, use kotlin-stdlib artifact instead)

I use kotlin-stdlib-jdk8 and it works in the IDE. Just for testing purposes, using kotlin-stdlib instead does not change anything.

In addition, replacing implementation with compile fixes it.

In the post I linked at the top of the question, there’s a suggestion to use runtime in the fatJar task. So:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

The dependency is still not included, and the program crashes.

So why not add implementation as a configuration to copy from?

I tried. I ended up with this:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.implementation.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

And an exception:

Resolving configuration ‘implementation’ directly is not allowed

So, considering:

  • compile instead of implementation works
  • The runtime exception is from Kotlin not being in the classpath
  • It works in the IDE
  • Adding an implementation clause to the fatJar task crashes the compiling with a separate exception

How do I generate a jar with all dependencies in Gradle 4.4 when using the implementation keyword?

Answer

Have you tried the Shadow Plugin like:

shadowJar {
  manifest {
     attributes 'Implementation-Title': 'rpi-sense-hat-lib',
            'Implementation-Version': version,
            'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
  }
  configurations = [project.configurations.compile, project.configurations.runtime]
}

Edit:

You can also do this (as described in an answer for this question):

configurations {
    fatJar
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    testImplementation group: 'junit', name: 'junit', version: '4.12'

    fatJar "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.fatJar.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

but then you have to repeat all implementation dependencies as fatJar dependencies. For your current project it’s fine since you have only one dependency, but for anything bigger it will become a mess…

Edit 2:

As @Zoe pointed out in the comments this is also an acceptable solution:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                   'Implementation-Version': version,
                   'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.runtimeClasspath.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

however make notice as per source code that runtimeClasspath is a combination of runtimeOnly, runtime and implementation, which may or may not be desirable depending on the situation – for instance you may not want to include runtime dependencies because they are provided by the container.