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

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

❯ 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?

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?

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?

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 the App class defined inside the App.kt file; this is not the class you need to run, this is just a plain old Kotlin class that doesn’t have a main method;
  • the AppKt.class is generated from the App.kt file itself; the source file contains a top-level function main which has to go inside some compiled class, as there are no top-level functions on the JVM; so the Kotlin compiler generates an AppKt 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

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Forketyfork

Software developer @ JetBrains Space. I mostly write about Java and microservice architecture. Occasional rants on software development in general.