Creating a standalone (runnable) Kotlin .jar file with IntelliJ and Gradle
Posted by Simon Larsén in Programming
I've recently started dabbling in some Kotlin, and have found it a very pleasant
experience. One of the first things I wanted to do was to create a standalone
.jar file, including the Kotlin runtime and any other dependencies. This,
as it turns out, was a bit tricky. In this short article, I will walk you
through creating a small command line application using the
awesome clikt library, and then packaging
it into a standalone .jar.
Setting up
Start out with creating a new project by going to File -> New -> Project, select
Gradle in the leftmost menu bar (i.e. not Kotlin), and then tick the Kotlin
box in the Additional Libraries and Frameworks menu. Then just fill in any
GroupId, ArtifactId and Version (I will use slarse, app and 0.1 for these
fields, respectively). Then just click Next with the defaults until the
project is created.
Initial Gradle configuration
In the project root, you should now have a file called build.gradle, which
looks something like this:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
group 'slarse'
version '0.1'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Before we can compile a project with clikt, we need to add it as a dependency.
We can do that by adding compile "com.github.ajalt:clikt:1.5.0" in the
dependencies section. It should now look like this:
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "com.github.ajalt:clikt:1.5.0"
}
Then hit the little refresh symbol in the bottom left corner
(should say Refresh Gradle Project when you hover your mouse over it) to
download the new dependency. And that's it for now!
We'll get back to the gradle.build file once we
want to configure our jar task, but let's create the app first!
Creating the application
Let's make this easy: we'll just use the sample application available from the
clikt documentation. It looks like this:
class Hello : CliktCommand() {
val count: Int by option(help="Number of greetings").int().default(1)
val name: String by option(help="The person to greet").prompt("Your name")
override fun run() {
for (i in 1..count) {
echo("Hello $name!")
}
}
}
fun main(args: Array<String>) = Hello().main(args)
Create a Kotlin file called main.kt at src/main/kotlin/main.kt and paste
the above code into it. Note that we are using the default package here
(i.e. not defining a package) for the sake of simplicity.
For this to compile, we will need to add the following imports at the top:
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.prompt
import com.github.ajalt.clikt.parameters.types.int
And that's it for the application, you should now be able to run it as usual.
When running it, there should appear a prompt in the terminal saying Your name:.
With that out of the way, the only thing left to do is to package our
fantastic application into a standalone .jar file.
Packaging the application into a standalone .jar file
This is actually not very difficult, but you need to know what to do. We need
to create a so-called "fat" jar, which includes both the Kotlin runtime and the
clikt library. We also need to specify the name of our main class.
jar {
manifest {
attributes 'Main-Class': 'MainKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Note that the class file generated by Kotlin for a file called something.kt
will be SomethingKt.class, which is why our main class is called MainKt.
With that in mind, the manifest section is self-explanatory: we specify
the main class. The from section collects all compile dependencies
(that we specified in the dependencies section) and package them with the
.jar file. The little piece of logic in the lambda is to properly add
directories and .jar files, respectively (directories are just added,
.jar files are unzipped and added).
Important: The main class file must be specified with its fully qualified name. For example, if I were to define
main.ktin the packagese.slarse, then I would need to putse.slarse.MainKtinstead of justMainKtin the manifest.
Anyway, that's really all we need to do.
It should now be possible to run the jar Gradle task to produce a .jar file
in build/libs/<ArtifactId>-<Version> (so in my case it is at
build/libs/app-0.1.jar). And that's it, hope it helped someone!
Full source code and build.gradle
Here are both of the files we wrote in this tutorial, in their entirety.
// main.kt
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.prompt
import com.github.ajalt.clikt.parameters.types.int
class Hello : CliktCommand() {
val count: Int by option(help="Number of greetings").int().default(1)
val name: String by option(help="The person to greet").prompt("Your name")
override fun run() {
for (i in 1..count) {
echo("Hello $name!")
}
}
}
fun main(args: Array<String>) = Hello().main(args)
// build.gradle
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
group 'se.slarse'
version '0.1'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "com.github.ajalt:clikt:1.5.0"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
jar {
manifest {
attributes 'Main-Class': 'MainKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}