How to Run
Standalone Kotlin
with Gradle

topher-lamey

Topher Lamey|January 15, 2021

Need to run a standalone Kotlin app as a fat jar in a Gradle project? Check out how we handled that!

Recently we wanted to add a specific kind of alerting to some of our build process via a third-party product. As part of that, we needed to run some of our Kotlin code in the third-party product as a standalone application. The third party had a limitation in that they only support this kind of execution using java -jar file.jar directly with a single jar file.

Luckily, java easily supports this via jar files with:

  • An application’s entry point via the Main-Class property in the jar’s manifest file

  • Packing any third-party classes in the jar (AKA a ‘fat’ jar).

We use Gradle for our build tool, and have used the application plugin to build and run arbitrary standalone applications. In this case, we already had the class defined in Kotlin with a main function used by Gradle’s application plugin that we wanted to run in this new third-party system.

Our main class is in a file com/stackhawk/example/Main.kt:

Kotlin
fun main(args: Array<String>) {
    println("Hello World!")
}

Note that unlike Java, Kotlin takes that file name and modifies for Java referencing in Gradle.

Our build.gradle.kts file then uses it with the application plugin like this:

Kotlin
plugins {
    application
}

application {
    mainClassName = "com.stackhawk.example.MainKt" // Really Main.kt
}

With that, we can do this at the shell:

Shell
> ./gradlew run
Hello World!

We can’t use Gradle in this third-party system, so we need to produce a jar file with the Main-Class property set in the manifest to the same class. We can do that using the Gradle jar task:

Kotlin
tasks.jar { // could also be a new task rather than the default one
    manifest {
        attributes["Main-Class"] = "com.stackhawk.example.MainKt"
    }
}

But we also have a bunch of third-party dependencies that we need to pack into that jar file. Luckily, the jar task can be expanded to include them in our jar.

Kotlin
tasks.jar { // could also be a new task rather than the default one
    manifest {
        attributes["Main-Class"] = "com.stackhawk.example.MainKt"
    }

    from(sourceSets.main.get().output)
    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
    })
}

Now we can do this:

Shell
> ./gradlew build
> java -jar build/libs/example-0.0.1.jar
Hello World!

Which is exactly how the third-party system wants to run Java/Kotlin applications.

Here’s a complete example that supports both ./gradlew run and java -jar $OUTPUT_JARFILE:

Kotlin
val main = "com.stackhawk.example.MainKt"

application {
    mainClassName = main
}

tasks.jar {
    manifest {
        attributes["Main-Class"] = main
    }

    from(sourceSets.main.get().output)
    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
    })
}

Once we had this set up in the project, we were able to generate a jar that worked as expected in the third-party alerting system. Since then, we’ve had other reasons, such as one-off and scheduled jobs, to use a jar in this manner and this Gradle set up has worked for those uses cases as well.

Ready to Test Your App


Topher Lamey  |  January 15, 2021