3

I used sbt-assembly on a project where I have some java 14 jars, and my local machine has JDK 8 as the default JDK.

The sbt assembly task was successful and produced a fat jar.

When I run it with JDK 8, I get the error:

Exception in thread "main" java.lang.UnsupportedClassVersionError: javafx/event/EventTarget has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0

JDK 11 (version 55.0) is the one I need. And sure enough, when I set JDK 11 on my shell, I can run the fat jar.

Is there a way to be explicit about the target JDK version in the build.sbt file? Also, I'm surprised that even though I have Java 14 jars in the dependency, the application runs fine on JDK 11. Is it just an example of Java's supreme backwards compatibility in action? I would like to know what else could be at work.

This is what my build.sbt looks like

name := "scalafx-app"

version := "0.1"

scalaVersion := "2.13.3"

scalacOptions += "-Ymacro-annotations"

useCoursier := false
assemblyMergeStrategy in assembly := {
  case "module-info.class" => MergeStrategy.concat
  case x =>
    val oldStrategy = (assemblyMergeStrategy in assembly).value
    oldStrategy(x)
}
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.1.1"

lazy val osName = System.getProperty("os.name") match {
  case n if n.startsWith("Linux") => "linux"
  case n if n.startsWith("Mac") => "mac"
  case n if n.startsWith("Windows") => "win"
  case _ => throw new Exception("Unknown platform!")
}

lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "web")


lazy val root = (project in file("."))
  .settings(
    libraryDependencies += scalaTest % Test,

    // scalafx
    libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19",
    libraryDependencies ++= javaFXModules.map(m =>
      "org.openjfx" % s"javafx-$m" % "14.0.1" classifier(osName) withJavadoc()
    ),
    libraryDependencies += "org.scalafx" %% "scalafxml-core-sfx8" % "0.5",

    // javafx custom components
    libraryDependencies += "com.jfoenix" % "jfoenix" % "9.0.9",
    libraryDependencies += "org.kordamp.ikonli" % "ikonli-javafx" % "11.4.0",
    libraryDependencies += "org.kordamp.ikonli" % "ikonli-material-pack" % "11.4.0",

    // json parsing
    libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.0",
    libraryDependencies += "com.squareup.moshi" % "moshi" % "1.9.3",

      // logging
    libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
    libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3",

  )
karanveer41
  • 221
  • 1
  • 2
  • 6

1 Answers1

3

A JAR is just like a zip of classes, each class is the one that you can check with javap to see which JDK version they need by looking at the value of the "major version" field; see this.

If you want to ensure the classes are compiled to a specific Java version, you can use the release & target scalac options.

Like this:

// Ensure the compiler emits Java 8 bytecode.
scalacOptions ++= Seq("-release", "8", "-target:8")
  • release is used to specify which Java sdtlib is used.
  • target is used t specify which bytcode version is emitted.
  • 1
    I tested the option by specifying 8, and was happy to see the assembly task fail because it didn't find some of the newer stdlib methods. Then changed it to 11, and assembly succeeded. This is great way to be explicit about the assembly output. Thanks! – karanveer41 Aug 13 '20 at 14:56