61

I've written a simple Kotlin source file in order to get started, and a Gradle script file. But I can't figure out how to add the main function to the manifest, so that the JAR could be self-executable.

Here is my build.gradle script :

buildscript { repositories { mavenCentral() } dependencies { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.9.66' } } apply plugin: "kotlin" repositories { mavenCentral() } dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib:0.9.66' } jar { manifest { attributes 'Main-Class': 'com.loloof64.kotlin.exps.ExpsPackage' } } 

Here is my com.loloof64.kotlin.exps.Multideclarations.kt:

package com.loloof64.kotlin.exps class Person(val firstName: String, val lastName: String) { fun component1(): String { return firstName } fun component2(): String { return lastName } } fun main(args: Array < String > ) { val(first, last) = Person("Laurent", "Bernabé") println("First name : $first - Last name : $last") } 

When I launch the JAR from terminal (java -jar MYJar.jar) I get the following stack trace, saying that the Kotlin reflection library classes are missing, and indeed they have not been added to the JAR. It seems that I am missing the kotlin-compiler artifact classes from the final JAR, and also the kotlin-stdlib sources, but I don't know how to adapt the Gradle build.

$> java -jar build/libs/kotlin_exps.jar Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/reflect/jvm/internal/InternalPackage at com.loloof64.kotlin.exps.ExpsPackage.<clinit>(MultiDeclarations.kt) Caused by: java.lang.ClassNotFoundException: kotlin.reflect.jvm.internal.InternalPackage at java.net.URLClassLoader$1.run(URLClassLoader.java:372) at java.net.URLClassLoader$1.run(URLClassLoader.java:361) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:360) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more 

I am using Kotlin 0.9.66 and Gradle 2.1.

3
  • Note that the classname generated by Kotlin for a top-level function such as main is the package defined in the same file, plus the filename with KT appended. In your case this would be com.loloof64.kotlin.exps.MultideclarationsKT. You can change this behavior by adding to the top of the file @file:JvmName("OtherName") to define a new classname for all top-level functions in a file. Commented Dec 31, 2015 at 0:57
  • You append the two characters Kt not KT... that is upper case K and lower case t. It is an easy error to make, but will cause the jar execution to fail. I had this problem myself on occasion due to imperfect typing. Commented Sep 12, 2018 at 19:58
  • 1
    See this rather comprehensive answer. Commented Feb 12, 2022 at 13:06

8 Answers 8

38

Add the plugin application, then set the mainClassName as

mainClassName = '[your_namespace].[your_arctifact]Kt' 

For instance, suppose you have placed the following code in a file named main.kt:

package net.mydomain.kotlinlearn import kotlin import java.util.ArrayList fun main(args: Array<String>) { println("Hello!") } 

your build.gradle should be:

apply plugin: 'kotlin' apply plugin: 'application' mainClassName = "net.mydomain.kotlinlearn.MainKt" 

In fact Kotlin is building a class to encapsulate your main function named with the same name of your file - with Title Case.

Sign up to request clarification or add additional context in comments.

6 Comments

Can you update your answer to the new naming convention for the how Kotlin creates classes for toplevel functions. In his example, that would now be: com.loloof64.kotlin.exps.MultideclarationsKT as the classname. This is true for Kotlin 1.0 beta and beyond.
Updated for Kotlin 1.0 Beta and newer. Thanks Jayson.
This shouldn't be the accepted answer; it's correct but not the full picture: It's required to 'collect' the Kotlin runtime files to make a complete executable jar, as below in @loloof64 's example.
This does not work anymore. I'll remove my downvote if you fix it.
@AdamArold I tested with 1.1.51 and it works. What version are you referring to? Thx
|
30

I've found the workaround (thanks to MkYong website)

  1. The gradle script needed the kotlin-compiler artifact as a dependency
  2. The gradle script needed a way to collect all kotlin files and put them into the jar.

So i get with the following gradle script :

buildscript { repositories { mavenCentral() } dependencies { classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.1-2' } } apply plugin: "kotlin" repositories { mavenCentral() } dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.1-2' } jar { manifest { attributes 'Main-Class': 'com.loloof64.kotlin.exps.MultideclarationsKT' } // NEW LINE HERE !!! from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } } 

5 Comments

for me it worked without the compiler dependency, collecting the files for the jar was enough
Can confirm that the compiler dependency is not needed, collecting the files is enough.
Where is the jar after executing the task?
Placement of the jar file depends on the method to build the jar. If you execute the gradle jar task it is put into the directory build/libs. The build directory is at the same level as the build.gradle file. If you construct the jar mapping inside the IntelliJ IDE as an artifact and build the artifact with IntelliJ, then it is placed inside the out/artifacts directory at the level of the IntelliJ project
Specifically, I found that after adding this jar_block to the build.gradle file, then I could create a successful jar either using the IntelliJ artifact mechanism or by building the jar file with gradle task mechanism.
26

None of the above solution worked for me while building Artifact.

IDE version IntelliJ IDEA 2019.1.1.

To fix the issue, do the following

Steps

Step 1 - Create Artifact

  1. Go to File -> Project Structure -> Artifacts

  2. Click the + -> JAR -> From modules with dependencies

enter image description here

  1. Select your program's Main Class

enter image description here

Step 2 - Change MANIFEST Path

  1. Change value of Directory for META-INF/MANIFEST.MF to your project root.

    For example , from /your/project/directory/src/main/kotlin to /your/project/directory

enter image description here

  1. Press OK,then Press Apply and OK.

Step 3 - Build Artifact

  1. Finally, Go to Build -> Build Artifacts -> [your-artifact-name] -> Build.

The generated JAR file can be found in the out/artifact/[your-artifact-name] directory. (y)

2 Comments

Thank you very much!!! After several attempts, that was the only thing that worked for me!
Kotlin is such a cool language, but it has a horrendous build system (magic recepies all around). Thanks for the help!
7

This is for Kotlin DSL (build.gradle.kts).
Note that the first 3 methods here modify the existing Jar task of Gradle.

Method 1: Placing library files beside the result JAR

This method does not need application or any other plugins.

tasks.jar { manifest.attributes["Main-Class"] = "com.example.MyMainClass" manifest.attributes["Class-Path"] = configurations .runtimeClasspath .get() .joinToString(separator = " ") { file -> "libs/${file.name}" } } 

Note that Java requires us to use relative URLs for the Class-Path attribute. So, we cannot use the absolute path of Gradle dependencies (which is also prone to being changed and not available on other systems). If you want to use absolute paths, maybe this workaround will work.

Create the JAR with the following command:

./gradlew jar 

The result JAR will be created in build/libs/ directory by default.

After creating your JAR, copy your library JARs in libs/ sub-directory of where you put your result JAR. Make sure your library JAR files do not contain space in their file name (their file name should match the one specified by ${file.name} variable above in the task).

Method 2: Embedding the libraries in the result JAR file (fat or uber JAR)

This method too does not need any Gradle plugin.

tasks.jar { manifest.attributes["Main-Class"] = "com.example.MyMainClass" val dependencies = configurations .runtimeClasspath .get() .map(::zipTree) // OR .map { zipTree(it) } from(dependencies) duplicatesStrategy = DuplicatesStrategy.EXCLUDE } 

Creating the JAR is exactly the same as the previous method.

Method 3: Using the Shadow plugin (to create a fat or uber JAR)

plugins { id("com.gradleup.shadow") version "8.3.0" } // Shadow task depends on Jar task, so these configs are reflected for Shadow as well tasks.jar { manifest.attributes["Main-Class"] = "org.example.MainKt" } 

Create the JAR with this command:

./gradlew shadowJar 

See Shadow documentations for more information about configuring the plugin.

Method 4: Creating a new task (instead of modifying the Jar task)

tasks.create("MyFatJar", Jar::class) { group = "my tasks" // OR, for example, "build" description = "Creates a self-contained fat JAR of the application that can be run." manifest.attributes["Main-Class"] = "com.example.MyMainClass" duplicatesStrategy = DuplicatesStrategy.EXCLUDE val dependencies = configurations .runtimeClasspath .get() .map(::zipTree) from(dependencies) with(tasks.jar.get()) } 

Running the created JAR

java -jar my-artifact.jar 

The above solutions were tested with:

  • Java 17
  • Gradle 7.1 (which uses Kotlin 1.4.31 for .kts build scripts)

See the official Gradle documentation for creating uber (fat) JARs.
For more information about manifests, see Oracle Java Doc: Working with Manifest files.
For difference between tasks.create() and tasks.register() see this post.

Note that your resource files will be included in the JAR file automatically (assuming they were placed in /src/main/resources/ directory or any custom directory set as resources root in the build file). To access a resource file in your application, use this code (note the / at the start of names):

  • Kotlin
    val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText() // Alternative ways: // val vegetables = object{}.javaClass.getResource("/vegetables.txt").readText() // val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText() // val vegetables = object{}.javaClass.getResourceAsStream("/vegetables.txt").reader().readText() 
  • Java
    var stream = MyClass.class.getResource("/vegetables.txt").openStream(); // OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt"); var reader = new BufferedReader(new InputStreamReader(stream)); var vegetables = reader.lines().collect(Collectors.joining("\n")); 

Comments

2

In case you happen to be dumb, like me:

Don't create your IntelliJ run configuration as an Application. IntelliJ will assume it's Java & it will never work. Instead, use the "Kotlin" entry.

Comments

2

Thanks, for me it worked adding the jar section

jar { manifest { attributes 'Main-Class': 'com.photofiles.Application' } from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } } 

No need for the application plugin.

1 Comment

The main-class name needs to end with Kt. So above it should be ApplicationKt.
2

If using Gradle with the Kotlin DSL, then my duplicate question has an answer of:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { kotlin("jvm") version "1.2.51" id("com.github.johnrengelman.shadow") version "2.0.4" } group = "xxx.yyy" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { implementation(kotlin("stdlib-jdk8")) } tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" } tasks.withType<ShadowJar> { manifest.attributes.apply { put("Implementation-Title", "Gradle Jar File Example") //put("Implementation-Version" version) put("Main-Class", "HelloKotlinWorld.App") } 

Which is, I think, the simplest solution. Oh, perhaps you're using just Kotlin itself and not the DSL.

Comments

1

For anyone using the Kotlin MP plugin here is your code

jvm{ jvmJar { manifest{ attributes 'Main-Class':'Class path here' } } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.