@guilgaly is right. You need only ordinary Maven dependencies and that’s what I’ve done in my example (that libraryDependencies ++= ...javafx...
thing). If you go to Getting Started with JavaFX then under “Runtime images” → “Non-Modular project” you’ll see the following:
Non-modular application
Since Java 9, applications should be modular, and distributed with tools like jlink. However, if you have a non-modular JavaFX project or you can’t use jlink because you have non-modular dependencies where the automatic module naming convention doesn’t apply, you can still do a fat jar.
As explained here, in order to create a runnable jar with all the required JavaFX dependencies, you will need to use a launcher class that doesn’t extend from Application.
Maven
If you develop your JavaFX applications using Maven, you don’t have to download the JavaFX SDK. Just specify the modules and the versions you want in the pom.xml file, and the build system will download the required modules, including the native libraries for your platform.
I haven’t used Mill yet so I don’t know how to configure it. SBT seems to be unaware of --add-modules
and just uses -classpath
all the time.
I have prepared another example, this time it uses SBT, OpenJFX and java.net.http
at once.
build.sbt
name := "javafx-in-scala"
version := "0.1"
scalaVersion := "2.13.3"
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.2.1" classifier osName
)
HelloWorld.scala
import javafx.application.Application
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.net.http.HttpResponse.BodyHandlers
import javafx.event.ActionEvent
import javafx.scene.{Scene, control}
import javafx.scene.control.{Button, TextArea}
import javafx.scene.layout.{StackPane, VBox}
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: _*)
}
private val client = HttpClient.newHttpClient
private val request = HttpRequest.newBuilder
.uri(URI.create("https://httpbin.org/uuid"))
.setHeader("accept", "application/json")
.build
override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Hello Java 11!")
val vBox = new VBox()
val outputView = new TextArea()
val btn = new Button()
btn.setText("Download new UUID4")
btn.setOnAction((_: ActionEvent) => {
client
.sendAsync(request, BodyHandlers.ofString())
.thenAccept(response => {
// not sure if Platform.runLater is needed here, probably it is
outputView.setText(response.body())
})
})
val root = new StackPane
root.getChildren.add(new VBox(btn, outputView))
primaryStage.setScene(new Scene(root, 500, 150))
primaryStage.show()
}
}
Run using sbt run
. It will show a window with a button and a text pane. After clicking the button the text pane will be filled with fresh contents downloaded using java.net.http
HTTP client.