Here’s a quick tutorial on how to package your java application and dependencies into single fat JAR (shadow JAR, capsule, however it’s called) with gradle.

Easy solution using built-in JAR gradle plugin

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

What it means:
* jar – packaging task available in gradle as part of java plugin
* from – copies passed FileTree into JAR
* configurations – holds all configurations (set of dependencies)
* compile – I used compile configuration for this particular example, but this could be any other or even a separate configuration just for fat JAR
* zipTree – this returns zipped file tree object, because JAR files essentially are ZIP files with specific file arrangement and content

While this is nice one-liner that uses only built-in gradle stuff, it will work only in cases when there are no name clashes in all dependencies. For example, if more than one dependency to be put in fat-JAR implements Java SPI service interface you will have multiple resources with the same name and only last one will make it into fat JAR.

Using Shadow JAR gradle plugin

buildscript {
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.1'
    }
}

apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
    mergeServiceFiles()
}

Internally this gradle plugin does the same thing as previous one-liner, plus:
* It collates SPI service files
* inherits configuration from jar task so that you may define manifest in standard familiar way:

jar {
    manifest {
        attributes(
            'Main-Class': "com.example.CraneBookingApplication"
        )
    }
}

Using Shadow JAR plugin with Windows

There is important caveat for Windows users however with previous method. Shadow JAR uses \n as line separator when merging SPI files. Consequently, when JAR is run on Windows JVM will not be able to correctly read file line-by-line. To avoid this, we define custom service file transformer in build.gradle that will use \r\n as line separator:

import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator
import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer

class WindowsTransformer extends ServiceFileTransformer {

    @Override
    void transform(String path, InputStream is, List<Relocator> relocators) {
        def lines = is.readLines()

        relocators.each {rel ->
            if (rel.canRelocateClass(new File(path).name)) {
                path = rel.relocateClass(path)
            }
            lines.eachWithIndex { String line, int i ->
                if (rel.canRelocateClass(line)) {
                    lines[i] = rel.relocateClass(line)
                }
            }
        }

        lines.each { line -> serviceEntries[path]
                .append(new ByteArrayInputStream((line + "\r\n").getBytes()))
        }
    }
}

shadowJar {
//    mergeServiceFiles() - should not be used, as it uses default transformer
    transform(WindowsTransformer.class)
}
Categories: Build

2 Comments

Mihail · November 20, 2018 at 15:09

Will your last version work on Linux or only on Windows? Is it possible to create a universal one?

    romanmarkunas · November 20, 2018 at 22:59

    The last section is a workaround to collate SPI files for JARs that will be run on windows platform. For Linux just applying plugin and building artifact with shadowJar must be sufficient. However you can always use last one as a safest measure, since most Linux distros understand \r\n line endings.

Leave a Reply

Your email address will not be published. Required fields are marked *