A runnable jar with Kotlin and Gradle
In this article, I’ll describe how to build a runnable jar from a Kotlin application using Gradle, and try to clarify some points of confusion and errors you may face in the process. I’ll describe both Groovy and Kotlin ways of configuring the build scripts.
Initial setup
It’s pretty easy to generate a sample Kotlin project with a runnable application. Just fire up your console, create a directory (let’s name it hello
), go inside, and run gradle init
:
mkdir hello
cd hello
gradle init
This is the summary of configuration choices for an application written in Kotlin and built using a Kotlin script (just select a different option on the “Select build script DSL” stage if you want to use Groovy as your build script language):
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Scala
6: Swift
Enter selection (default: Java) [1..6] 4
Split functionality across multiple subprojects?:
1: no - only one application project
2: yes - application and library projects
Enter selection (default: no - only one application project) [1..2] 1
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Kotlin) [1..2] 2
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] yes
Project name (default: hello):
Source package (default: hello):
> Task :init
Get more help with your project: https://docs.gradle.org/7.5.1/samples/sample_building_kotlin_applications.html
BUILD SUCCESSFUL in 26s
2 actionable tasks: 2 executed
If you now go inside the app/src/main/kotlin/hello
directory, you’ll see a generated Kotlin source file with the following contents. Suppose this is the application that you want to package and run as a jar file:
/*
* This Kotlin source file was generated by the Gradle 'init' task.
*/
package hello
class App {
val greeting: String
get() {
return "Hello World!"
}
}
fun main() {
println(App().greeting)
}
Building the jar
If you run the gradle jar
command, it will generate the jar
file located inside the app/build/libs
directory:
❯ gradle jar
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
Since there’s a main
function defined in your application, you’d like to make this jar
file runnable. When you say:
java -jar app/build/libs/app.jar
you’d like to see the Hello World!
output.
However, if you do it without any additional configuration, you’ll see the following error:
❯ java -jar app/build/libs/app.jar
no main manifest attribute, in app/build/libs/app.jar
How to make the jar runnable?
Every runnable jar
file has to have a special MANIFEST.MF
file inside the META-INF
directory, where the main executable class is specified as one of the attributes. Otherwise, the JVM won’t know which of the classes to run when you call java -jar
.
But surely you don’t want to write and package this obscure file by hand! Thankfully, Gradle can generate it for you and place it in the proper directory inside the jar file. Here’s how you specify this main class with Kotlin:
tasks.jar {
manifest {
attributes("Main-Class" to "hello.AppKt")
}
}
And here’s the Groovy version:
jar {
manifest {
attributes("Main-Class": "hello.AppKt")
}
}
Why doesn’t mainClass
attribute work?
But wait, there’s already a mainClass
attribute (or mainClassName
, in older versions of Gradle) in your build.gradle.kts
file:
application {
// Define the main class for the application.
mainClass.set("hello.AppKt")
}
Or, for a Groovy-based configuration:
application {
// Define the main class for the application.
mainClass = 'hello.AppKt'
}
Why do you need to specify the main class one more time?
The mainClass
attribute only defines which class to run when you say gradle run
. It’s intended for simplifying the development process. It has nothing to do with building the jar artifact.
A related issue is a typical error for a Groovy configuration when you say something like:
// this will not work!
jar {
manifest {
mainClassName = 'hello.AppKt'
}
}
This, unfortunately, will pass the build, but will not work.
Groovy is a dynamically typed language, so it allows you to set this property, as it’s coming from the application
plugin. But Groovy is just not well-typed enough to figure out that this property is of no use for the jar
task of the Gradle build.
This is exactly why the Kotlin language was introduced as an alternative for writing the build scripts: this is a statically typed language, so it’s able to prevent such kinds of errors.
Why is AppKt
, not App
, the main class of the application?
Since your application is written in Kotlin, you have to specify the AppKt
class, not the App
class, as an entry point of your application. This may look confusing because the Kotlin class inside the generated App.kt
file is named App
.
If you look inside the app/build/classes
directory generated after the build, you’ll see there are two distinct files, App.class
and AppKt.class
:
- the
App.class
file is generated from theApp
class defined inside theApp.kt
file; this is not the class you need to run, this is just a plain old Kotlin class that doesn’t have amain
method; - the
AppKt.class
is generated from theApp.kt
file itself; the source file contains a top-level functionmain
which has to go inside some compiled class, as there are no top-level functions on the JVM; so the Kotlin compiler generates anAppKt
class that will hold this function.
The AppKt
class is not explicitly defined inside your source code. But this is the entry point of your application, as it contains the main
method.
If you’ve made a mistake and specified the App
class as the main class, you’ll get an error like this:
❯ java -jar app/build/libs/app.jar
Error: Main method not found in class hello.App, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
Conclusion
In this article, I’ve tried to answer some of the questions and clarify some points of confusion related to Gradle configuration for executable jars. Please leave any ideas for improvement in the comments!