Build Application with Gradle - Single Executable Jar with scripts

Gradlle Build Application
Photo by 贝莉儿 DANIST on Unsplash

In this tutorial, we will learn how to create executable fat Jar with startup scripts.
As you have already known, Gradle is a popular automation tool for building applications. A customized build script can be created using it either in Groovy or Kotlin DSL.
To start with, we will use Gradle’s Application plugin to create executable JVM application build. It implicitly applies the Java plugin and distribution plugin. It packages the application code with dependencies and startup scripts.
First, create a new build script file build.gradle.kts using Intellij editor.
As you have noticed here filename ends with .kts extension which means this script is written using Kotlin DSL.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.3.50"
}
group = "demo"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}
dependencies {
    implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}
Above is the initial script code when the file is created.
Now next step is to add application plugin. To use it add keyword application to plugins block.
plugins {
    kotlin("jvm") version "1.3.50"
    application}
Application plugin compulsorily requires an entry point for the application to be configured i.e. mainClassName.
application {
    mainClassName = "demo.testapp.MainKt"
}
Till now we have configured Application plugin which will generate the distribution but it won't create fatjar/uber executable Jar as we expected. You might think that why do we need fatjar and doing so what is the benefit?
To answer that it creates a single executable Jar which includes application code and all of its dependencies and resources. It also avoids dependencies classpath conflicts and makes distribution management easy.
Now, we are going to use Gradle Shadow Plugin to create fatjar. To use this plugin add below line to plugins block -
plugins {
    kotlin("jvm") version "1.3.50"
    application
    id("com.github.johnrengelman.shadow") version "5.0.0"
}
We can also configure this plugin by specifying extra parameters to task ShadowJar like -
tasks {
    named<ShadowJar>("shadowJar") {
        archiveBaseName.set("demo")
        mergeServiceFiles()
    }
}
Here, we are setting the archive base name as demo. So it will create an executable Jar that starts with the given base name.
mergeServiceFiles — Multiple dependencies may use the same service descriptor file name, to merge such contents in the same output file this function is useful.
To execute our build script use below command -
./gradlew clean installShadowDist
After executing this will create fatjar with name demo-1.0-SNAPSHOT-all.jar in the directory “./build/install/testapp-shadow/lib/”
and two startup scripts in the directory “./build/install/testapp-shadow/bin/”
testapp — startup for unix systems.
testapp.bat — startup script for windows systems.
As we have reached the end of this tutorial, I hope, this is helpful.
Full code for build.gradle.kts script file for your reference —
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.3.50"
    application
    id("com.github.johnrengelman.shadow") version "5.0.0"
}
group = "demo"
version = "1.0-SNAPSHOT"

application {
    mainClassName = "demo.testapp.MainKt"
}
repositories {
    mavenCentral()
}
dependencies {
    implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}
tasks {
    named<ShadowJar>("shadowJar") {
        archiveBaseName.set("demo")
        mergeServiceFiles()
    }
}

Comments

  1. Its a bad idea to just use mergeServiceFiles unconstrained like that, if you open up the resulting jar and have a look, there will be typically be a ton of bloat from things that aren't implementation stage dependencies, like gradle.

    You should leverage the include filter like below, and bring in just the things you need.

    mergeServiceFiles {
    setPath("META-INF/services")
    include("io.grpc.*")
    }

    ReplyDelete
    Replies
    1. Thanks for the suggestion.
      Will explore it to improvise this tutorial further.

      Delete

Post a Comment