Scala, JDK 11 and JPMS

Are there any comprehensive tutorials or guides on how to deal with JPMS when using Java libs in Scala projects?

1 Like

I don’t think anyone has done a unified writeup (if one exists, I’d love to know about it).

In the meantime, places to go digging include:

2 Likes

Thank you, I will look into that.

I suspect that JPMS won’t be tackled for Scala 2 and I don’t see any hint on the road map for Dotty/Scala 3. Do you know if there is anything planned? It is quite painful if you use a modern language like Scala but than you have to fall back to old libraries just because of JPMS.

Can you describe what the blocker exactly is for you, that’s causing you need to fall back to old libraries?

If there is a library which supports JPMS, in my case OpenJFX, I cannot use it because at runtime I get some error from the JVM.

Going over the links you provided I understand that Scala - at the moment - just ignores JPMS at compile-time and does not know about it at runtime.

I think I might recall reading at least one previous thread about that. Perhaps if you search the forum (or the rest of the internet) for the specific error message, you will find something. And/or if you post the specifics here, perhaps someone will recognize the problem and be able to suggest a solution or workaround.

What is your evidence that the root cause of your problem is that something is missing from Scala?

Well, when using javac it works perfectly fine because it supports module-info.java, and problems are already apparent to compile time. In this situation, the Scala compiler let you down, not telling you that there is an issue where usually the Scala compiler has your back in any situation.

So you found out about a problem at runtime rather than compile time. That much I understand. (It’s https://github.com/scala/scala-dev/issues/529.)

But what’s causing you to say that you “have to fall back to old libraries”? That’s what I’m not following here. Or does that wording no longer apply…?

What is the error?

Do you have -opt:l:inline and -opt-inline-from enabled? If so, I’m wondering if excluding the OpenJFX packages from being inlined will help. You’d do that by adding !javafx.** to -opt-inline-from.

1 Like

Taking my example: If I fall back to JavaFX 8, which does not use JPMS, because it was built before Java 9, I have no problems.

That demonstrates that falling back to an old version of JavaFX is one way to solve your problem. But is it the only way to solve your problem? Are you actually blocked from using Java 11? If so, why?

I’m very focused on your use of the words “have to”, because if people are actually being blocked in their Java 11 upgrades — and not just encountering temporary obstacles for which solutions or workarounds exist — then we reeeeeeeally want to know about it.

I’m not sure what you could call a viable alternative.

I can use JavaFX 8, but than I cannot upgrade to Java 11, since there is no Java 11 JDK/JRE which bundles JavaFX and the JavaFX 11 builds need Java 11.

What I currently do is using Azul’s ZuluFX 11 which bundles JavaFX 11 and somehow than JPMS is not an issue. But this is strongly discouraged by the guys from Gluon, their official way to go is to use JDK/JRE 11 together with JavaFX as a library dependency. And as soon as I do that JPMS gets in my way.

To cut it short: For me, at the moment, there is a (rather unrecommended) workaround, if it comes to JavaFX. But that’s just one library out there. What if Apache Commons decides to introduce JPMS? I guess there won’t be any JDK/JRE which bundles Apache Commons.

Some Google searches seem to indicate that you are very far from being the first person to try to use JavaFX 11 with Scala, and although I’m not expert on the details, there seems to be a fair amount of advice out there about how to do it. For example, have you tried starting the JVM with appropriate --module-path and --add-modules flags, as per https://stackoverflow.com/a/57759613/86485 ? Or what about the advice at https://stackoverflow.com/a/57628433/86485 ? )(Those are just a couple of the search results that seemed likely to be relevant; I didn’t sift through them extensively.)

What about https://github.com/scalafx/scalafx-hello-world , I see that in 2019 you previously received advice to look at that (at Example build.sbt for JDK11), how did that pan out? It seems to be regularly updated.

It seems to me that you from the starting point of “the Scala compiler isn’t telling me at compile time that there is a problem”, you are jumping to the conclusion “there is no way forward, I must downgrade to Java 8”. This seems like an enormous jump to me, and I find the conclusion implausible.

Does https://github.com/scalafx/scalafx-hello-world work for you, or not? The repo readme says it works on JDK 11. If it doesn’t work for you, what error message are you getting?

4 Likes

Then the Scala compiler won’t identify module problems at compile time, but that will not mean the problems are unresolvable.

You seem to have confused “the Scala compiler doesn’t identify module issues at compile time” with “Scala simply doesn’t work with JPMS at all”. The former is true, the latter is not.

2 Likes

In programming there is always a solution to any problem, the question is, how far one wants to go for it.

My original question was if anywhere are guidelines for finding how to work around issues one can get using JPMS in Scala applications. From there the discussion faded, due to additional questions by me - sorry for that.

But I hope you can confirm, too, that the current situation with Java interoperability beyond Java 8 is not quite satisfying and that this is not even a target in Scala 3, even years after JPMS was introduced.

As long as we execute Scala programs on the JRE we need to take runtime problems as well into account. Coding and compiling stuff is just one side of the coin.

Java after 8 and JPMS are a thing, even if we, or me, don’t like it, and we must find a way to work with it. But often I have the impression, that people of the Scala community response is slightly annoyed if they are asked about problems related to Java 9+ and I just don’t understand why. A bunch of Github issues and Stackoverflow articles are not useful documentation, especially not for beginners.

(Please don’t misunderstand my statement, I didn’t judge the way you responded to me, I am more than grateful that you take your time to try to help, day after day, as so many others do here.)

I know, but for me one of Scala’s USP is: Don’t panic, the Scala compiler has your back! :slight_smile:

I’ve just run current version of https://github.com/scalafx/scalafx-hello-world without problems under Ubuntu and Java 11:

java --version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)

It downloaded and used OpenJFX 14 as a dependency under Java 11 and the window with a styled word “ScalaFX” shows as expected.

For some reason SBT didn’t use --add-modules or other JPMS related switches. The command line SBT used to run the example was just using -classpath:

/usr/lib/jvm/adoptopenjdk-11-hotspot-amd64/bin/java -classpath /tmp/sbt_96f9d9ee/job-1/target/f527dcdf/45981425/scalafx-hello-world_2.13-14-R19.jar:/tmp/sbt_96f9d9ee/target/3be29489/2580ecdc/scala-library-2.13.2.jar:/tmp/sbt_96f9d9ee/target/52a708fa/9f34c840/scalafx_2.13-14-R19.jar:/tmp/sbt_96f9d9ee/target/895779cf/d2bb0048/javafx-base-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/203f6638/801033d9/javafx-controls-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/9752eeeb/06685b3d/javafx-fxml-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/830ce1fa/0ac9fa17/javafx-graphics-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/92b31ba3/4b4e6e15/javafx-media-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/dbd1a50f/e5742b86/javafx-swing-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/5b1fe36e/b7aedf40/javafx-web-14.0.1-linux.jar:/tmp/sbt_96f9d9ee/target/bf8b00d6/f40a418b/scala-reflect-2.13.2.jar:/tmp/sbt_96f9d9ee/target/a0573e5e/c33f033a/javafx-graphics-14.0.1.jar:/tmp/sbt_96f9d9ee/target/c22b10d8/2b313230/javafx-controls-14.0.1.jar:/tmp/sbt_96f9d9ee/target/8d27d0ad/545d5987/javafx-base-14.0.1.jar:/tmp/sbt_96f9d9ee/target/296c091a/f2382648/javafx-media-14.0.1.jar hello.ScalaFXHelloWorld
1 Like

I know about ScalaFX, but to me there are some issues:

  1. ScalaFX does not work well with FXML and the Scene Builder.
  2. Using ScalaFX applies the “Another wrapper will solve the problem.” pattern, which I try to avoid.
  3. ScalaFX is just another dependency; I believe we should reduce the number of dependencies in our software.
  4. I know about ScalaFXML, but I don’t know how well maintained it is, plus: 3).

Okay, so let’s remove ScalaFX from the equation. I’ve hacked some ideas together and arrived at a working solution. Setup is the same as it was, i.e OpenJDK 11, OpenJFX 14, Ubuntu and SBT.

In directory /tmp/javafx-in-scala I’ve created two files:

build.sbt:

name := "javafx-in-scala"

version := "0.1"

scalaVersion := "2.13.2"

scalacOptions ++= Seq("-unchecked", "-deprecation", "-Xcheckinit", "-encoding", "utf8", "-feature")

// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
fork := true

// Determine OS version of JavaFX binaries
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!")
}

// Add JavaFX dependencies
lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
libraryDependencies ++= javaFXModules.map( m=>
  "org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
)

HelloWorld.scala

import javafx.application.Application
import javafx.event.ActionEvent
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage

// must have different name than launched Application subclass
object HelloWorldLauncher {
  def main(args: Array[String]): Unit = {
    new HelloWorld().mainForwarder(args)
  }
}

class HelloWorld extends Application {
  def mainForwarder(args: Array[String]): Unit = {
    Application.launch(args: _*)
  }

  override def start(primaryStage: Stage): Unit = {
    primaryStage.setTitle("Hello World!")
    val btn = new Button
    btn.setText("Say 'Hello World'")
    btn.setOnAction((_: ActionEvent) => {
      System.out.println("Hello World!")
    })
    val root = new StackPane
    root.getChildren.add(btn)
    primaryStage.setScene(new Scene(root, 300, 250))
    primaryStage.show()
  }
}

sbt run gives following output:

$ sbt run
Warning: a legacy coursier cache was found at /home/piotrek/.coursier/cache/v1 and is currently being used.

Support for that cache location will be removed in coursier 2.0.0 final, whose release is imminent.

Follow the instructions at
  https://github.com/coursier/cache-migration#cache-migration
in order to migrate your cache to the newer location.


[info] welcome to sbt 1.3.11 (AdoptOpenJDK Java 11.0.7)
[info] loading settings for project global-plugins from idea.sbt ...
[info] loading global plugins from /home/piotrek/.sbt/1.0/plugins
[info] loading project definition from /tmp/javafx-in-scala/project
[info] loading settings for project javafx-in-scala from build.sbt ...
[info] set current project to javafx-in-scala (in build file:/tmp/javafx-in-scala/)
[info] Compiling 1 Scala source to /tmp/javafx-in-scala/target/scala-2.13/classes ...
[info] running (fork) HelloWorldLauncher 
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[info] Hello World!
[success] Total time: 8 s, completed 30 maj 2020, 12:24:45

Hello World! lines shows after you click the Hello World button.

Once again SBT uses old (pre JPMS) -classpath switch instead of new (JPMS related) --add-modules and the likes.

3 Likes

I’m not involved with the planning for Scala 3 (except SIP stuff which this isn’t), so I don’t know about that part. Is there a https://github.com/lampepfl/dotty-feature-requests ticket?

I think there’s still a good chance that https://github.com/scala/scala-dev/issues/529 will progress in the 2.13.x series. I think one reason it hasn’t is that it hasn’t been a blocker for people — which is why I was so keen to discover if this was truly a blocker for you or not. But it’s certainly worth improving regardless.

I apologize if I came off as irritated.

Thanks @tarsa for the sample code — I think this will be helpful to future seekers (and perhaps to @dubaut too).

1 Like