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.kt
in the packagese.slarse
, then I would need to putse.slarse.MainKt
instead of justMainKt
in 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) }
}
}