59

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.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
loloof64
  • 5,252
  • 12
  • 41
  • 78
  • 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. – Jayson Minard Dec 31 '15 at 00: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. – J.E.Tkaczyk Sep 12 '18 at 19:58
  • 1
    See [this rather comprehensive answer](https://stackoverflow.com/a/71092054/8583692). – Mahozad Feb 12 '22 at 13:06

8 Answers8

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.

StefMa
  • 3,344
  • 4
  • 27
  • 48
Guildenstern70
  • 1,715
  • 18
  • 18
29

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) } }
}
loloof64
  • 5,252
  • 12
  • 41
  • 78
  • 3
    for me it worked without the compiler dependency, collecting the files for the jar was enough – Stefan Paul Noack Mar 16 '15 at 23:56
  • Can confirm that the compiler dependency is not needed, collecting the files is enough. – Czyzby May 02 '16 at 19:41
  • Where is the jar after executing the task? – Franmcod Nov 22 '17 at 12:44
  • 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 – J.E.Tkaczyk Sep 12 '18 at 20:10
  • 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. – J.E.Tkaczyk Sep 12 '18 at 20:18
18

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)

Community
  • 1
  • 1
theapache64
  • 10,926
  • 9
  • 65
  • 108
3

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.github.johnrengelman.shadow") version "6.0.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 Documentation: 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"));
    
Mahozad
  • 18,032
  • 13
  • 118
  • 133
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.

Tim Keating
  • 6,443
  • 4
  • 47
  • 53
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.

paucls
  • 41
  • 1
  • 2
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.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Thufir
  • 8,216
  • 28
  • 125
  • 273
1

For anyone using the Kotlin MP plugin here is your code

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

}
Alaa Sbateen
  • 59
  • 1
  • 3