scalajarsbtdroolssbt-assembly

Drools fat jar nullpointer KieServices


I'm generating a fat jar of my project using sbt assembly. Then, when trying to run my jar file, i get a nullpointer on this line:

val kieServices: KieServices = KieServices.Factory.get
val kieContainer: KieContainer = kieServices.getKieClasspathContainer

I've already tried adding a kie.conf, but this does not help. I am not using maven or a pom file etc. And am using scala sbt.

Running drools' latest version.

build.sbt:

ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "2.13.10"

lazy val root = (project in file("."))
  .settings(
    name := "untitled",
    libraryDependencies ++= Seq(
      "org.drools" % "drools-core" % "8.31.1.Final",
      "org.drools" % "drools-compiler" % "8.31.1.Final",
      "org.drools" % "drools-decisiontables" % "8.31.1.Final",
      "org.drools" % "drools-mvel" % "8.31.1.Final",
      "org.drools" % "drools-model-compiler" % "8.31.1.Final",
      "org.kie" % "kie-api" % "8.31.1.Final"
    ),
    resolvers in Global ++= Seq(
      "Sbt plugins" at "https://dl.bintray.com/sbt/sbt-plugin-releases",
    ),
    Compile / packageBin / mainClass := Some("src.Main"),
    Compile / run / mainClass := Some("src.Main")
  )
  .settings(
    assembly / assemblyJarName := "myJar.jar",
    assembly / assemblyMergeStrategy := {
      case PathList("META-INF", xs@_*) => MergeStrategy.discard
      case _ => MergeStrategy.first
    },
  )

project/plugins.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")

src/main/scala/src/Main.scala (sorry not sorry):

package src

import org.kie.api.KieServices
import org.kie.api.runtime.KieContainer

object Main extends App {
  val kieServices: KieServices = KieServices.Factory.get
  val kieContainer: KieContainer = kieServices.getKieClasspathContainer
}

Solution

  • Debugging shows that

    import scala.jdk.CollectionConverters._
    
    ServiceLoader.load(classOf[KieServices], classOf[KieServices].getClassLoader)
     .asScala.size
    

    is 1 when I do sbt run but 0 when I do java -jar myJar.jar.

    In the former case KieServices.Factory.get returns org.drools.compiler.kie.builder.impl.KieServicesImpl@...some...hashcode... but in the latter case it returns null.

    Try to add a file src/main/resources/META-INF/services/org.kie.api.KieServices with content

    org.drools.compiler.kie.builder.impl.KieServicesImpl
    

    One more issue is that, on contrary to sbt package, sbt assembly is missing this file from the assembly jar. So try to unpack myJar.jar, put manually this file as myJar.jar/META-INF/services/org.kie.api.KieServices and zip the jar back (myJar.jar/META-INF/MANIFEST.MF should exist but myJar.jar/META-INF/services is probably missing: https://github.com/sbt/sbt-assembly/issues/11)

    java.util.ServiceLoader.load() function is useless and only returns empty result

    How to include a config file in the "META-INF/services" folder of a JAR using Maven


    It's missing from assembly jar because of

    assembly / assemblyMergeStrategy := {
      case PathList("META-INF", xs@_*) => MergeStrategy.discard
    

    in your build.sbt. So modify the strategy:

    assembly / assemblyMergeStrategy := {
      case PathList("META-INF", "services", "org.kie.api.KieServices") => MergeStrategy.concat
      case PathList("META-INF", xs@_*) => MergeStrategy.discard
      case _ => MergeStrategy.first
    }
    

    and service file will be included into assembly jar.


    Actually, the service file already exists in the dependency: ~/.cache/coursier/v1/https/repo1.maven.org/maven2/org/drools/drools-compiler/8.31.1.Final/drools-compiler-8.31.1.Final.jar:META-INF/services/org.kie.api.KieServices

    So you shouldn't add it manually, just do case PathList("META-INF", "services", "org.kie.api.KieServices") => MergeStrategy.concat.

    There are different service files in drools-compiler-8.31.1.Final.jar:META-INF/services/ so be careful with case PathList("META-INF", xs@_*) => MergeStrategy.discard, it's possible you'll have more problems later if you ignore service files.


    Try

    assembly / assemblyMergeStrategy := {
      case PathList("META-INF", "services", xs@_*) => MergeStrategy.concat
      case PathList("META-INF", xs@_*) => MergeStrategy.discard
      case _ => MergeStrategy.first
    }