Search code examples
kotlincommand-linecompilation

How to run a Kotlin application using classes from JAR files


I've just started using Kotlin and I'm having some issues understanding how JAR files work. I have two files:

hello.kt

class HelloWorld {
    public fun speak() {
        println("hello world")
    }
}

main.kt

fun main() {
    val hello = HelloWorld()
    hello.speak()
}

My project structure is:

root
|-hello.kt
|-main.kt

My intention is build the Kotlin application spreading its classes amongst dirrent JAR files. I've used

kotlinc hello.kt -d hello.jar
kotlinc main.kt -cp hello.jar -include-runtime -d main.jar

to build the JAR files. These two commands execute without any error. I'm having trouble understanding how to execute the application.

So far I've tried

java -cp hello.jar -jar main.jar

to run the application but I get this error:

Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld
        at MainKt.main(main.kt:2)
        at MainKt.main(main.kt)
Caused by: java.lang.ClassNotFoundException: HelloWorld
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
        ... 2 more

I've also tried to follow the answer to this question which shows the same issue, but running

java -cp main.jar:hello.jar MainKt

gives me the following error

Error: Could not find or load main class MainKt
Caused by: java.lang.ClassNotFoundException: MainKt

I'm new to Kotlin and JVM in general but I cannot find a solution.


Solution

  • As a first note, you would usually use a build tool such as Gradle to do the compilation and packaging for you. However, for learning purposes, it's always nice to know how things fit together under the hood.

    In your case, you're compiling things correctly. However, there are issues with how you run your application using the java command. First, you cannot mix -cp and -jar, as per the documentation:

    When you use -jar, the specified JAR file is the source of all user classes, and other class path settings are ignored. If you're using JAR files, then see jar.

    In short, you have to choose between using -jar with an executable jar file containing all the classpath information, or using -cp with all the jars you need + the main class name.

    Since you're not fiddling with jar manifests, you're not creating an executable jar, so you should stick to the -cp option. You need to provide all the jars needed in the -cp option (separated with : on linux or mac, and with ; on Windows), and the main class name at the end of the command:

    java -cp "hello.jar;main.jar" MainKt
    

    The name of the main class MainKt is derived from the name of the file containing your main() function and the package name inside the file, as defined (albeit subtly) in the Kotlin documentation.

    You can also check this answer for more details.