{{announcement.body}}
{{announcement.title}}

AdoptOpenJDK 11 + OpenJFX + NetBeans: Part 2

DZone 's Guide to

AdoptOpenJDK 11 + OpenJFX + NetBeans: Part 2

Now that we have an application up and running, we're going to try something a little unorthodox: removing the module-info.

· Open Source Zone ·
Free Resource

In part one we looked at getting a modular JavaFX application built and running from NetBeans. In this post we'll look at generating, from Maven, a runtime to execute our application. 

Assuming you followed the steps successfully in part one, you should have a running application. In this section we are going to start by removing the module-info from the root of the project. I know, counter-intuitive, right? We are going to use ModiTect, a Maven plugin authored for the very purpose of managing module descriptors and runtime images.

Compared to authoring module descriptors by hand, using ModiTect saves you work by defining dependence clauses based on your project's dependencies, describing exported and opened packages with patterns (instead of listing all packages separately), auto-detecting service usages and more.

<plugin>
  <groupId>org.moditect</groupId>
  <artifactId>moditect-maven-plugin</artifactId>
  <version>1.0.0.Beta2</version>
  <executions>
    <execution>
      <id>add-module-info-to-deps</id>
      <phase>package</phase>
      <goals>
        <goal>add-module-info</goal>
      </goals>
      <configuration>
        <overwriteExistingFiles>true</overwriteExistingFiles>
        <jvmVersion>${jvm.version}</jvmVersion>
        <module>    
          <moduleInfo>
            <name>com.luff.javafx.test</name>
            <opens>com.luff.javafx.test;</opens>
          </moduleInfo>
        </module>
      </configuration>
    </execution>
  </executions>
</plugin>


We'll add the plugin and some generic configuration to create the module-info and set us back to the same position we were, having previously manually detailed the module-info. Now, however, we want to go further and turn this into a runtime image.

<execution>
  <id>create-runtime-image</id>
  <phase>package</phase>
  <goals>
    <goal>create-runtime-image</goal>
  </goals>
  <configuration>
    <modulePath>
      <path>${project.build.directory}/modules</path> <!--our modules-->
    </modulePath>
    <modules> <!--everything we need-->
      <module>${moduleName}</module>
      <module>java.base</module>
      <module>javafx.base</module>
      <module>javafx.fxml</module>
      <module>javafx.swing</module>
      <module>javafx.controls</module>
      <module>javafx.graphics</module>
      <module>javafx.web</module>
    </modules>
    <excludedResources>
      <pattern>glob:/com.luff/**</pattern>
    </excludedResources>
    <launcher>
      <name>Test</name> <!--the name of the application executable-->
      <module>${moduleName}/${mainClass}</module>
    </launcher>
    <stripDebug>true</stripDebug>
    <compression>2</compression>
    <no-header-files>true</no-header-files>
    <no-man-pages>true</no-man-pages>
    <jdepsExtraArgs>
      <args>--strip-native-commands</args>
    </jdepsExtraArgs>
    <outputDirectory>${project.build.directory}/jlink-image</outputDirectory>
  </configuration>
</execution>


Add the create-runtime-image execution right after the add-module-info execution; they will run sequentially.

At this point, go ahead and run a clean:build   again — you'll find it take a little longer, but this time we are rewarded with a new jlink-image directory in the build location. Navigate through that and you'll see the a folder structure similar to the below image.

Image title

Note the Test executable and if you execute it, we have our little app.

That was simple, right? The thing is, projects are not simple! They usually include stuff that is not always at the bleeding edge of development. Say, for example, I wanted to include a dependency that is not modular.

<dependency>
  <groupId>eu.hansolo</groupId>
  <artifactId>Medusa</artifactId>
  <version>8.3</version>
</dependency>
@FXML
private Clock clock;

private static final ScheduledExecutorService TICKING = Executors.newSingleThreadScheduledExecutor();;
private final Runnable run = FXMLController.this::changeDisplay;

@Override
public void initialize(URL url, ResourceBundle rb) {
  TICKING.scheduleAtFixedRate(run, 0, 1, TimeUnit.SECONDS);
}

private void changeDisplay() {
  Platform.runLater(() -> clock.setTime(ZonedDateTime.now()));
}
<?xml version="1.0" encoding="UTF-8"?>

<?import eu.hansolo.medusa.Clock?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.luff.javafx.test.FXMLController">
  <children>
    <Clock fx:id="clock" animated="true" layoutX="14.0" layoutY="50.0" lcdFont="STANDARD" prefHeight="169.0" prefWidth="292.0" skinType="TEXT" AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="50.0">
      <customFont>
        <Font name="Arial" size="12.0" />
      </customFont></Clock>
  </children>
</AnchorPane>


Let's see what happens if we try to build the code: 

 Failed to execute goal org.moditect:moditect-maven-plugin:1.0.0.Beta2:create-runtime-image (create-runtime-image) on project Test: Execution create-runtime-image of goal org.moditect:moditect-maven-plugin:1.0.0.Beta2:create-runtime-image failed: Execution of jlink failed -> [Help 1] 

Moditect did do it's part: it generated the module-info for our project.

module Test {
  requires Medusa;
  requires javafx.base;

  requires transitive javafx.fxml;
  requires transitive javafx.graphics;

  exports com.luff.javafx.test;

}


Here is the issue. We need to tell Moditect that we have a jar that is not a module, it needs a module descriptor.

<plugin>
  <groupId>org.moditect</groupId>
  <artifactId>moditect-maven-plugin</artifactId>
  <version>1.0.0.Beta2</version>
  <executions>
    <execution>
      <id>add-module-info-to-deps</id>
      <phase>package</phase>
      <goals>
        <goal>add-module-info</goal>
      </goals>
      <configuration>
        <overwriteExistingFiles>true</overwriteExistingFiles>
        <jvmVersion>${jvm.version}</jvmVersion>
        <modules>
          <module>
            <artifact>
              <groupId>eu.hansolo</groupId>
              <artifactId>Medusa</artifactId>
            </artifact>
            <additionalDependencies>
              <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-controls</artifactId>
                <version>${javafx.version}</version>
                <classifier>mac</classifier>
              </dependency>
              <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-base</artifactId>
                <version>${javafx.version}</version>
                <classifier>mac</classifier>
              </dependency>
              <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-graphics</artifactId>
                <version>${javafx.version}</version>
                <classifier>mac</classifier>
              </dependency>
            </additionalDependencies>
            <moduleInfo>
              <name>eu.hansolo.Medusa</name>
            </moduleInfo>
          </module>
        </modules>
        <module>    
          <moduleInfo>
            <name>com.luff.javafx.test</name>
            <opens>com.luff.javafx.test;</opens>
          </moduleInfo>
        </module>
      </configuration>
    </execution>


Notice the significantly expanded modules section. Medusa relies upon JavaFX and needs a module-info attaching that describes that dependency.

module Medusa {
  requires transitive javafx.base;
  requires transitive javafx.controls;
  requires transitive javafx.graphics;

  exports eu.hansolo.medusa;
  exports eu.hansolo.medusa.events;
  exports eu.hansolo.medusa.skins;
  exports eu.hansolo.medusa.tools;

}


With this modified section the project will now build, the correct module descriptors will be built and the code will run. To generate the binary, we simply add our newly-created module to the modules in the create-runtime-image-section.

<modules>
  <module>${moduleName}</module>
  <module>java.base</module>
  <module>javafx.base</module>
  <module>javafx.fxml</module>
  <module>javafx.swing</module>
  <module>javafx.controls</module>
  <module>javafx.graphics</module>
  <module>javafx.web</module>
  <module>eu.hansolo.Medusa</module>
</modules>


Again, run a clean and build, have a look for the outputted binary and....

Image title

In part 3 we will look at putting this all into a distrabutable package across a couple of different platforms.

Topics:
netbeans ,javafx ,adoptopenjdk ,tutorial ,modules ,modular ,integration ,open source

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}