I am trying to upgrade a JavaFX project from the 8 Java version to the 11 version. It works when I use the "run" Gradle task (I followed the Openjfx tutorial), but when I build (with the "jar" Gradle task) and execute (with "java -jar") a jar file, the message "Error: JavaFX runtime components are missing, and are required to run this application" appears.
Here is my build.gradle file :
group 'Project'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.11
repositories {
mavenCentral()
}
def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
platform = 'win'
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'mac'
}
dependencies {
compile "org.openjfx:javafx-base:11:${platform}"
compile "org.openjfx:javafx-graphics:11:${platform}"
compile "org.openjfx:javafx-controls:11:${platform}"
compile "org.openjfx:javafx-fxml:11:${platform}"
}
task run(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = "project.Main"
}
jar {
manifest {
attributes 'Main-Class': 'project.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
compileJava {
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls,javafx.fxml'
]
}
}
run {
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls,javafx.fxml'
]
}
}
Do you know what I should do ?
With Java/JavaFX 11, the shadow/fat jar won't work.
As you can read here:
This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module:
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
If that module is not present, the launch is aborted.
Hence, having the JavaFX libraries as jars on the classpath is not allowed
in this case.
What's more, every JavaFX 11 jar has a module-info.class file, at the root level.
When you bundle all the jars content into one single fat jar, what happens to those files with same name and same location? Even if the fat jar keeps all of them, how does that identify as a single module?
There is a request to support this, but it hasn't been addressed yet: http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs
Provide a means to create an executable modular “uber-JAR” that contains more than one module, preserving module identities and boundaries, so that an entire application can be shipped as a single artifact.
The shadow plugin still does make sense to bundle all your other dependencies into one jar, but after all you will have to run something like:
java --module-path <path-to>/javafx-sdk-11/lib \
--add modules=javafx.controls -jar my-project-ALL-1.0-SNAPSHOT.jar
This means that, after all, you will have to install the JavaFX SDK (per platform) to run that jar which was using JavaFX dependencies from maven central.
As an alternative you can try to use jlink to create a lightweight JRE, but your app needs to be modular.
Also you could use the Javapackager to generate an installer for each platform. See http://openjdk.java.net/jeps/343 that will produce a packager for Java 12.
Finally, there is an experimental version of the Javapackager that works with Java 11/JavaFX 11: http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-September/022500.html
EDIT
Since the Java launcher checks if the main class extends javafx.application.Application, and in that case it requires the JavaFX runtime available as modules (not as jars), a possible workaround to make it work, should be adding a new Main class that will be the main class of your project, and that class will be the one that calls your JavaFX Application class.
If you have a javafx11 package with the Application class:
public class HelloFX extends Application {
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
Scene scene = new Scene(new StackPane(l), 400, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Then you have to add this class to that package:
public class Main {
public static void main(String[] args) {
HelloFX.main(args);
}
}
And in your build file:
mainClassName='javafx11.Main'
jar {
manifest {
attributes 'Main-Class': 'javafx11.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Now you can run:
./gradlew run
or
./gradlew jar
java -jar build/libs/javafx11-1.0-SNAPSHOT.jar
The final goal is to have the JavaFX modules as named modules on the module path, and this looks like a quick/ugly workaround to test your application. For distribution I'd still suggest the above mentioned solutions.
With the latest versions of JavaFX, you can use two Gradle plugins to easily distribute your project (javafxplugin and jlink).
With these plugins, you can:
Create a distributable zip file that contains all the needed jar files: it requires a JRE to be executed (with the bash or batch script)
Create a native application with Jlink for a given OS: a JRE is not needed to execute it, as Jlink includes a "light" JRE (including only the needed java modules and dependencies) in the distribution folder
I made an example on bitbucket, if you want an example.
[Edit: For the latest versions of JavaFX, please check my second answer]
If someone is interested, I found a way to create jar files for a JavaFX11 project (with Java 9 modules). I tested it on Windows only (if the application is also for Linux, I think we have to do the same steps but on Linux to get JavaFX jars for Linux).
I have a "Project.main" module (created by IDEA, when I created a Gradle project) :
src
+-- main
| +-- java
| +-- main
| +-- Main.java (from the "main" package, extends Application)
| +-- module-info.java
build.gradle
settings.gradle
...
The module-info.java file :
module Project.main {
requires javafx.controls;
exports main;
}
The build.gradle file :
plugins {
id 'java'
}
group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11
repositories {
mavenCentral()
}
def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
platform = 'win'
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'mac'
}
dependencies {
compile "org.openjfx:javafx-base:11:${platform}"
compile "org.openjfx:javafx-graphics:11:${platform}"
compile "org.openjfx:javafx-controls:11:${platform}"
}
task run(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = "main.Main"
}
jar {
inputs.property("moduleName", moduleName)
manifest {
attributes('Automatic-Module-Name': moduleName)
}
}
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls'
]
classpath = files()
}
}
task createJar(type: Copy) {
dependsOn 'jar'
into "$buildDir/libs"
from configurations.runtime
}
The settings.gradle file :
rootProject.name = 'Project'
And the Gradle commands :
#Run the main class
gradle run
#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar
#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"
Related
I started a new project with kotlin-multiplatform to create a library usable on iOS and Android using this tutorial :
https://play.kotlinlang.org/hands-on/Targeting%20iOS%20and%20Android%20with%20Kotlin%20Multiplatform/01_Introduction
It seems to work fine but I wanted to add the Serialization library mentioned at the end of the tutorial (https://github.com/Kotlin/kotlinx.serialization) and I can't make it work.
The setup guide in the library is not in Kotlin DSL so I tried different things to adapt the code but without success. Here is my project gradle :
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
And now my build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
}
kotlin {
//select iOS target platform depending on the Xcode environment variables
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
::iosArm64
else
::iosX64
iOSTarget("ios") {
binaries {
framework {
baseName = "SharedCode"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.13.0")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0")
}
sourceSets["iosMain"].dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:0.13.0")
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets
.getByName<KotlinNativeTarget>("ios")
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
I have no errors but I cannot use the library in my code.
Can someone please explain how to integrate this dependency or any dependency with this setup ? What do I do wrong ?
Note : I'm using Android Studio 3.5.1, Gradle 5.4.1, Kotlin 1.3.50.
Ok, so I found the issue.. just the version of the library.. 0.13.0 not 0.14.0. No error is thrown when you sync a wrong library version. I hope this post helps someone anyway.
I am trying to build a fat jar using the following in my Kotlin based gradle file.
val fatJar = task("fatJar", type = Jar::class) {
baseName = "safescape-lib-${project.name}"
// manifest Main-Class attribute is optional.
// (Used only to provide default main class for executable jar)
from(configurations.runtimeClasspath.map({ if (it.isDirectory) it else zipTree(it) }))
with(tasks["jar"] as CopySpec)
}
tasks {
"build" {
dependsOn(fatJar)
}
}
However, the fat jar has all the dependencies expanded out. I would like to have the jars included as is in a /lib directory but I cannot work out how to achieve this.
Can anyone give any pointers as to how I can achieve this?
Thanks
Well you are using zipTree in that map part of the spec, and it behaves according to the documentation: it unzips the files that are not a directory.
If you want the jars in /lib, replace your from with:
from(configurations.runtimeClasspath) {
into("lib")
}
In case anyone is using kotlin-multiplatform plugin, the configuration is a bit different. Here's a fatJar task configuration assuming JVM application with embedded JS frontend from JS module:
afterEvaluate {
tasks {
create("jar", Jar::class).apply {
dependsOn("jvmMainClasses", "jsJar")
group = "jar"
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis(),
"Main-Class" to mainClassName
)
)
}
val dependencies = configurations["jvmRuntimeClasspath"].filter { it.name.endsWith(".jar") } +
project.tasks["jvmJar"].outputs.files +
project.tasks["jsJar"].outputs.files
dependencies.forEach { from(zipTree(it)) }
into("/lib")
}
}
}
We have recently started using Gradle(1.12) to build our application components and the first place we have started is by doing EJB Deploy. This is what we are trying to achieve:
compile a sourceSet which is located at project directory /ejbModule ( This consists of the java classes and META-INF folder which contains ejb-jar.xml and other websphere related xml's.
Create a jar which will contain the above package and resources
run ejbdeploy.bat located at %WAS_HOME%\bin which takes input as jar created in step 2 which will create stubs / skeletons and re-package the jar at a different directory.
Now this is my gradle build file
def wasHome = 'C:/Data/IBM/WebSphere/AppServer'
def internalClasses = 'C:/GoldCopy1/Workspace/chimessharedlib'
def appPath = 'C:/GoldCopy1/Workspace/IEApp'
apply plugin:'java'
repositories {
flatDir {
dirs wasHome + '/lib'
dirs internalClasses
dirs appPath
}
}
dependencies {
compile name : 'j2ee'
compile name : 'commons-lang-2.3'
compile name : 'FW'
compile name : 'Common'
compile name : 'DA'
compile name : 'ST'
}
sourceSets {
main {
java {
srcDir '/ejbModule'
}
resources {
srcDir '/ejbModule'
}
}
}
task runEJBDeploy (type:Exec) {
commandLine wasHome + '/bin/ejbdeploy.bat', '/C'
def inputJar = "" + libsDir + File.separator + 'AR.jar';
println inputJar;
def argsList = [inputJar, "C:\\tpp" , "C:\\tpp\\deploy\\AR.jar" , "-complianceLevel", "5.0"]
args = argsList;
}
So by running the runEJBDeploy task above, I want to generate a command like below:
ejbdeploy.bat -cp "C:\tpp\FW.jar;C:\tpp\DA.jar;C:\tpp\Common.jar" C:\tpp\AR.jar C:\tpp C:\tpp\deployed\AR.jar -complianceLevel 5.0
So since FW, DA and Common are already provided in the dependencies { } section of the build.gradle file, is there a way to use those dependencies for appending it after "-cp " and then run the command?
I am very new to gradle so please also point out any other wrong approach taken which should be improved.
Thanks in advance
1.My project has two main class i want to build jar for each main class using gradle. my source has 2 files ValidationRule.java
SupportValidator.java both the file have one main class each i want to
build the jar for each main class
i can achieve the same from eclipse working fine
2.I want to load the source file for my project from 2 different folder,some part is there in one folder and remaining is
there in
another folder i.e like
project/src snd another folder outside the project(../../../SharedClass)
my script as follows
apply plugin: 'eclipse'
apply plugin: 'java'
sourceCompatibility = 1.6
archivesBaseName = 'Process_XY'
configurations {
configurations.compile.transitive = false
}
dependencies {
compile fileTree(dir:'/trunk/Solutions/project/Source/Binaries/CommonFunctions/build/libs', include: '*.jar')
compile fileTree(dir:'/trunk/Solutions/project/lib/GeoTools/geotools-2.7.4-bin/geotools-2.7.4', include: '*.jar')
compile "org.apache.hadoop:hadoop-core:1.0.3"
compile "commons-collections:commons-collections:3.2.1"
compile "commons-configuration:commons-configuration:1.6"
compile "commons-discovery:commons-discovery:0.2"
compile "commons-lang:commons-lang:2.4"
compile "commons-logging:commons-logging:1.1.1"
compile "commons-logging:commons-logging:1.0.4"
compile "log4j:log4j:1.2.16"
compile "com.vividsolutions:jts:1.8"
compile "commons-net:commons-net:1.4.1"
compile "org.apache.hadoop:hadoop-core:1.0.3"
compile "commons-httpclient:commons-httpclient:3.0.1"
compile "org.mortbay.jetty:servlet-api:2.5-20081211"
compile "org.apache.hbase:hbase:0.94.0"
compile "org.apache.zookeeper:zookeeper:3.4.3"
}
repositories {
mavenCentral()
maven { url "https://repository.cloudera.com/artifactory/cloudera-repos/" }
maven { url "http://repo.springsource.org/libs-release" }
maven { url "http://repo.springsource.org/libs-milestone" }
maven { url "http://repo.springsource.org/libs-snapshot" }
maven { url "http://www.datanucleus.org/downloads/maven2/" }
maven { url "http://oss.sonatype.org/content/repositories/snapshots" }
maven { url "http://people.apache.org/~rawson/repo" }
}
jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
manifest.attributes("Main-Class":"org.project.seismic.Process_XY")
}
sourceSets {
main {
java {
source = ['src/org', '../../../SharedClass/org']
}
}
}
above in sourceSets method i tried to load source from 2 folder but it
didnt worked
Thanks in advance..!!
How to achieve using gradle.
Ok, first of all, the source on a SourceDirectorySet takes another SourceDirectorySet. The srcDirs method however takes paths. Change that block to the following:
sourceSets {
main {
java {
srcDirs ['src/org', '../../../SharedClass/org']
}
}
}
And you can easily add a second jar task as follows:
task secondJar(type: Jar) {
name = other-main-jar
from ...
manifest.attributes(...)
}
assemble.dependsOn(secondJar)
This will register a new Jar task called secondJar and makes sure that when the project is assembled, this jar is also created.
I would like to migrate a gradle multi project to java 9. Instead of trying a big bang I would like to go step by step. My idea is to start from top (main module) and then work my way down. This implies, that I will have a main module in Java 9 while others are still in Java 8.
If my info is correct a good approach is to have the modules in java 8 integrated as automatic modules. It works fine with 3rd party libs as jar's, but I do not know how to teach gradle to do it with my own java 8 modules.
Imagine I got a Main Java 9 Module 'java9' and a Java 8 Library Module 'java8'
What I did so far:
'java8' build.gradle
apply plugin: 'java'
ext.moduleName = 'java8module.main'
jar {
inputs.property("moduleName", moduleName)
manifest {
attributes('Automatic-Module-Name': moduleName)
}
}
This gives me a java8 jar with a Module Name I decided for in the MANIFEST.MF
'java9' build.gradle
apply plugin: 'application'
mainClassName = 'com.thoughtblaze.java9.main.Main'
ext.moduleName = 'java9main.main'
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
]
classpath = files()
}
}
dependencies {
compile project (':java8')
}
'java9' module-info
module java9main.main {
requires java9module.main ;
requires java8module.main ;
}
The problem now is:
'java9' does not recognize java8 as a required module and therefore I cant access any class of the java8 module.
What do I miss here ?