gradlekotlingradle-kotlin-dslkotlin-dslhttp4k

Error with Building Fatjar Using Kotlin DSL


I am trying to build a fatjar using ShadowJar. My app and gradle code are below. I'm using Gradle 5.0 for the build. When I run ./gradlew run, the code works. When I run 'gradle shadowjar', and run the fatjar using 'java -jar' in the 'build/lib' folder, I get the below error.

I'm guessing the dependencies are not getting loaded in the fatjar? I have also used Groovy to build the Gradle file, and I get the same error.

Am I correct in that I am not including all of the dependencies in the fatjar file? If that is the case, any idea on how to modify the Gradle file to make sure this is included?

Gradle Kotlin DSL

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    // Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
    id("org.jetbrains.kotlin.jvm").version("1.3.21")

    id("com.github.johnrengelman.shadow") version "5.0.0"

    // Apply the application plugin to add support for building a CLI application.
    application
}

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
    mavenCentral()
}

dependencies {
    // Use the Kotlin JDK 8 standard library.
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // Use the Kotlin test library.
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    // Use the Kotlin JUnit integration.
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")

    implementation("org.http4k:http4k-core:3.143.1")
    implementation("org.http4k:http4k-server-jetty:3.143.1")
    implementation("org.http4k:http4k-client-okhttp:3.143.1")
    implementation("org.http4k:http4k-client-apache:3.143.1")

}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

application {
    // Define the main class for the application.
    mainClassName = "com.benito.AppKt"
}

tasks.withType<ShadowJar>  {
    archiveBaseName.set("${project.name}-all")
}

Kotlin Code

import org.http4k.core.HttpHandler
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.routing.bind
import org.http4k.routing.path
import org.http4k.routing.routes
import org.http4k.server.Jetty
import org.http4k.server.asServer

fun main(args: Array<String>) {
    println("Starting")


    val app: HttpHandler = routes(
        "/ping" bind Method.GET to { _: Request -> Response(OK).body("pong!") },
        "/greet/{name}" bind Method.GET to { req: Request ->
            val path: String? = req.path("name")
            Response(OK).body("hello ${path ?: "anon!"}")
        }
    )
    app.asServer(Jetty(8080)).start()
}

Error Message

2019-05-15 11:15:13.925:INFO::main: Logging initialized @287ms to org.eclipse.jetty.util.log.StdErrLog
2019-05-15 11:15:14.039:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2019-04-29T20:42:08.989Z; git: e1bc35120a6617ee3df052294e433f3a25ce7097; jvm 1.8.0_211-b12
Exception in thread "main" java.lang.ExceptionInInitializerError
    at org.eclipse.jetty.http.MimeTypes$Type.<init>(MimeTypes.java:103)
    at org.eclipse.jetty.http.MimeTypes$Type.<clinit>(MimeTypes.java:58)
    at org.eclipse.jetty.http.MimeTypes.<clinit>(MimeTypes.java:191)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:836)
    at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:278)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:167)
    at org.eclipse.jetty.server.Server.start(Server.java:418)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
    at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
    at org.eclipse.jetty.server.Server.doStart(Server.java:382)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.http4k.server.Jetty$toServer$3.start(jetty.kt:33)
    at com.benito.AppKt.main(App.kt:38)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
    at org.eclipse.jetty.http.PreEncodedHttpField.<clinit>(PreEncodedHttpField.java:70)
    ... 14 more

Solution

  • The problem here is that during the process of merging all the JARs in to one FatJAR, the contents of the META-INF folder in the http4k-core JAR (which contains the mime.types file used for matching a request to a content type) is somehow omitted or overridden by the merging process.

    This isn't http4k specific and is (quite) a common problem with the use of the shadow-jar gradle plugin. But it can be easily solved by just correctly configuring the plugin as below:

    shadowJar {
        mergeServiceFiles() // <-- this!
    }
    

    From the http4k side, the exception message generated has been changed to highlight this problem.