Building native applications for all PC and mobile platforms from a single JavaFX project with Gluon Mobile and GitHub Actions

The post “Starting a JavaFX Project with Gluon Tools” shows you how to start a Gluon Mobile Multiview project with a few clicks in IntelliJ IDEA thanks to the “Gluon plugin”.

In this post, we will use such a project and build it with GitHub Actions as a native application for Windows, MacOS, iOS, Linux, and Android from one single code base! Yes, that’s right, true “Write Once, Run Everywhere”!!!

The application

This application is built around a Java library I created last year for my book “Getting Started with Java on the Raspberry Pi” which helps you to calculate the value of a resistor from its color rings. This library is further described on “Resistor color codes and calculations as a Java Maven library”.

Based on “Gluon Mobile Multiview”

Based on a “Gluon Mobile Multiview” project, it was only a matter of hours to create a working application with two calculation views and one “About” view. Many thanks to José Pereda of Gluon who was so kind to improve my ugly proof-of-concept to a much better-looking layout with some clever tweaks and CSS improvements.

It’s not finished yet…

As this first version was created to have a proof-of-concept flow from JavaFX application to native applications and app store publications, the code is not fine-tuned yet…

There are still some things to do:

Any help and pull requests are welcome on the GitHub project ;-)

GitHub Actions

GitHub Actions allow you to automate your software workflows with Continuous Integration and Continuous Deployment right from GitHub.

By adding .yml-files to the directory “.github/workflows” in your project, these will be run as configured. A simple example is provided on “Introduction to GitHub Actions” which checks out the pushed code, installs the software dependencies, and runs bats -v:

name: learn-github-actions
on: [push]
    runs-on: ubuntu-latest
        - uses: actions/checkout@v2
        - uses: actions/setup-node@v1
        - run: npm install -g bats
        - run: bats -v


Gluon provides an easy and modern approach to develop Java Client applications. These applications can run on the JVM or can be converted to platform-specific native-images, which have a lightning-fast startup and take a fraction of space. Moreover, applications can also be targeted to Android, iOS, and embedded apart from all the desktop environments.

Gluon client plugin

The Gluon Client plugin leverages GraalVM, OpenJDK, and JavaFX by compiling the Java Client application and all its required dependencies into native code, so that it can be directly executed as a native application on the target platform.

One of the advantages is a much faster startup time, since the JVM no longer needs to be started. The resulting application will be entirely integrated into the native operating system.

More info is available on the docs pages on the Gluon website.

Gluon GitHub Build License Action

The GitHub Actions system allows to “plugin” build steps provided by third parties. These are GitHub-projects on their own which allow you to simplify your .yml-files.

Gluon created such a build action which you can integrate into the build process of your JavaFX native application: “gluon-build-license” to correctly use your Gluon license key in the build process. You will need to add this license to your GitHub project repository secrets.

This license-step is optional! If you don’t have one, a popup will be shown at startup of your application. You can request a free license to Gluon if you are working on a student or open-source project, as described on “Free Gluon Licenses”.

Build as a native application with GraalVM

Gluon Substrate takes away most of the complexity of using GraalVM Native Image. You can create an app in Java, test it on your desktop, and then compile and link the Java bytecode to a native image for a specific platform by defining a profile in Maven. The resulting binary can be deployed to the AppStore or Google Play.

The build process on GitHub Actions

For the sake of this post, all the different build processes are split into separate .yml-files, but GitHub Actions allows you to combine different “jobs” into one such file.

General description

The .yml-structure is pretty similar for all build types and has this structure:

Based on the name, you can get a badge which you can add to the README of your project or… this post:

Depending on the OS some additional steps are settings are required as described further.

Desktop applications

Linux JAR + native application

Action file: maven-ubuntu-linux.yml

For Linux, a list of extra libraries is required, which is done with an additional step after the download of GraalVM.

- name: Install libraries
  run: sudo apt install libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libgl-dev libgtk-3-dev libpango1.0-dev libxtst-dev

This build process produces both a JAR and a Linux native application. For the first one we run a Maven package:

- name: Build JAR with Maven
  run: mvn -B package

Because the build process is running on a Linux machine, we can define the target as desktop to build a native Linux application with the Gluon Maven client plugin and need to provide the GraalVM location:

- name: Gluon Build
  run: mvn -Pdesktop client:build client:package
    GRAALVM_HOME: ${{ env.JAVA_HOME }} 

Windows native application

Action file: maven-windows.yml

To build the Windows version of the application, Visual Studio is required, which can be done with two additional steps:

- name: Add msbuild to PATH
  uses: microsoft/setup-msbuild@v1.0.2
- name: Visual Studio shell
  uses: egor-tensin/vs-shell@v1

The rest of the script is similar to the Linux one and at the end, we only copy the exe-file to the package.

MacOS native application

Action file: maven-macos.yml

Xcode is required to build Apple applications. Again we can add this to our steps by using an existing action:

- uses: maxim-lobanov/setup-xcode@v1
    xcode-version: '11.7.0'

And the resulting file “target/client/x86_64-darwin/Resistor Calculator” is copied to the package.

Smartphone applications

When I started working on this post and project, I just wanted to reach successful native builds for all platforms. But then Gluon stepped in and pushed this a lot further and guess what? This application is now on both Google Play and the Apple App Store thanks to the work of Erwin Morrhey.

All the required steps to build AND upload to both the stores are included in the GitHub Actions!!!

You will need a developer account for both stores and the required keys. As I don’t have them (yet), a fork has been made by Gluon and these steps only run fully if the needed keys are provided as you can see on the fork in the GluonHQ GitHub repository.

iOS app (MacOS build)

Action file: maven-ios.yml

Additional settings are required which you can hide from your script by defining them in the secrets-section of your GitHub project, just like we already did with the Gluon license key.

Again we can use existing GitHub Actions to extend our previous “MacOS native” build file:

By using these with our secret keys, all the required steps can be run inside a single GitHub action. For instance, the final step to upload the application to TestFlight:

- uses: Apple-Actions/upload-testflight-build@master
  if: ${{ github.repository_owner == 'gluonhq' }} 
    app-path: target/client/arm64-ios/Resistor Calculator.ipa
    issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
    api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
    api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

Android app (Linux build)

Action file: maven-ubuntu-android.yml

Actions used in this file (which is based on the previous “Linux native” file):

The additional steps for Google Play include initialization of the Android Keystore:

- name: Setup Android Keystore
  if: ${{ github.repository_owner == 'gluonhq' }}
  id: android_keystore_file
  uses: timheuer/base64-to-file@v1
    fileName: 'my.keystore'
    encodedString: ${{ secrets.GLUON_ANDROID_KEYSTORE_BASE64 }}

And finally, the upload is done with:

- name: Upoad to Google Play
  if: ${{ github.repository_owner == 'gluonhq' }}
  uses: r0adkll/upload-google-play@v1
    serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}
    packageName: be.webtechie.resistorcalculatorapp
    releaseFiles: target/client/aarch64-android/gvm/Resistor Calculator.apk
    track: beta


The power of JavaFX combined with the Gluon tools and GitHub actions is amazing. Building and distributing a truly cross-platform application has never been easier! Really not a single code change is needed to run on different platforms. As you can see from the build processed, the exact same code is used to create native applications for both Windows, Linux, MacOS, iOS, and Android!

Gluon is also working on a sample using this work-flow, which is available on