Search code examples
javakotlinjava-platform-module-systemserviceloaderjavalin

Javalin with JPMS and ServiceLoader results in NoClassDefFoundError: kotlin/NoWhenBranchMatchedException


Running Javalin with JPMS and ServiceLoader leads to a NoClassDefFoundError: kotlin/NoWhenBranchMatchedException. The same code following two other approaches works fine, though (see at the end of this post for details):

  • No JPMS and ServiceLoader: if I run the very same Javalin application not as a JPMS module and without the ServiceLoader to resolve dependencies, then everything works just fine.

  • Console app: I can also run a console application which is a real JPMS module and uses the ServiceLoader to resolve dependencies.

Steps to reproduce the issue

(1) Clone main branch

(2) Try to run API in root folder of project

# Windows
.\gradlew.bat :api:run

# Linux
./gradlew :api:run

You should see the following error:

> Task :api:run FAILED
[main] INFO org.example.api.WebAPI - Hello World from WebAPI, yay :-)
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/NoWhenBranchMatchedException
        at [email protected]/io.javalin.core.JavalinConfig$Inner.<init>(JavalinConfig.java:77)
        at [email protected]/io.javalin.core.JavalinConfig.<init>(JavalinConfig.java:67)
        at [email protected]/io.javalin.Javalin.<init>(Javalin.java:54)
        at [email protected]/io.javalin.Javalin.create(Javalin.java:91)
        at [email protected]/io.javalin.Javalin.create(Javalin.java:78)
        at org.example.api/org.example.api.WebAPI.main(WebAPI.java:24)
Caused by: java.lang.ClassNotFoundException: kotlin.NoWhenBranchMatchedException
        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:520)
        ... 6 more
FAILURE: Build failed with an exception.

The following two approaches work fine, though:

(3) No JPMS and ServiceLoader: change to branch no-jpms-and-serviceloader and then you can successfully run the API via

# Windows
.\gradlew.bat :api:run

# Linux
./gradlew :api:run

(4) Console app: run it as a console application in either of the branches

# Windows
.\gradlew.bat :client:run

# Linux
./gradlew :client:run

Solution

  • I've created a Javalin GitHub issue describing the same issue. It's solved by now, but for convenience reasons I'll drop the solution here on Stackoverflow as well.

    Adding the following line to the module descriptor (module-info.java) of the api project solves the issue:

        requires io.javalin;
        requires com.fasterxml.jackson.databind;
        requires org.slf4j.simple;
        requires kotlin.stdlib;                        <-- simply add this line here
    
        uses org.example.services.api.PersonReader;
    

    The root cause seems to be that Javalin is not modularized yet. If you look closer into the module resolution (pass --show-module-resolution arg to java) you will see that Javalin is treated as an automatic module and the auto-generated requires statements for this automatic module do not include kotlin.stdlib.

    If Javalin ever becomes modularized this should no longer be an issue. Until then, this is a viable solution which can also be applied to other not-yet-modularized projects.