Kotlin is great for creating small command-line utilities, which can be packaged and distributed as normal JAR files. In this recipe, we will see how to do it using Gradle build system. Gradle build system is one of the most sophisticated build systems out there. It is the default build tool for Android and is designed to ease scripting of complex, multilanguage builds with a lot of dependencies (typical of big projects). It achieves the goal of automating your project without compromising on maintainability, usability, flexibility, extensibility, or performance. We will be using Gradle build system to create a self-extracting JAR file. This JAR file can be distributed to and run on any platform supporting Java.
How to build a self-executable JAR with Gradle and Kotlin
Getting ready
You need an IDE (preferably IntelliJ or Android Studio), and you need to tell it where your Kotlin files are present. You can do so by specifying it in the build.gradle file by adding the following:
sourceSets {
main.java.srcDirs += 'src/main/kotlin/'
}
The preceding lines are required if you have your Kotlin files separated from Java packages. This is optional, and you can continue working with Kotlin files under Java packages, but it’s a good practice to keep them separated.
We’ll be creating a very simple function that just prints Hello World! when executed. Since it’ll be a simple function, I am just adding it as a top-level main() function.
How to do it...
Let's go through these steps, with which we can create a self-executable JAR:
- We’ll create a simple class HelloWorld.kt having the main function, which just prints out “Hello world!”:
fun main(args:Array<String>){
println("Hello world")
}
- Now we need to configure a jar task, which Gradle build goes through to inform it of our entry to our project. In a Java project, this will be the path to the class where our main() function resides, so you will need to add this jar task in build.gradle:
jar {
manifest {
attributes 'Main-Class': 'HelloWorldKt'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
- After adding the preceding snippet to build.gradle, you need to run the following gradle command to create the jar file:
./gradlew clean jar
- The created jar file can be found in the build/libs folder. Now you can just run the java -jar demo.jar command to run the JAR file.
After you do that, you can see the output in the console:
How it works...
To make self-executable JARs, we need a manifest file called MANIFEST.MF in the META-INF directory. For our purposes here, we just need to specify the name of the Java class that contains the Java-based extractor program's main() method.
One might argue that even though we don’t have top-level class declaration, we are specifying it as HelloWorldKt in the code for the jar task:
manifest {
attributes 'Main-Class': 'HelloWorldKt'
}
The reason for putting the preceding code block in the jar task is that Kotlin compiler adds all top-level functions to respective classes for back-compatibility with JVM. So, the class generated by Kotlin compiler will have the filename, plus the Kt suffix, which makes it HelloWorldKt.
Also, the reason we added from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } in jar task is because we want Gradle to copy all of a JAR’s dependencies. The reason for doing so is that, by default, when Gradle (as well as Maven) packs some Java class files into a JAR file, it assumes that this JAR file will be referenced by an application, where all of its dependencies are also accessible in the classpath of the loading application. So, by specifying the preceding lines in jar task, we are telling gradle to take all of this JAR’s referenced dependencies and copy them as part of the JAR itself. In the Java community, this is known as a fat JAR. In a fat JAR, all the dependencies end up within the classpath of the loading application, so the code can be executed without problems. The only downside to creating fat JARs is their growing file size (which kind of explains the name), though it is not a big concern in most situations.