A minimal setup for a Windows desktop application compiled to a native image with GraalVM.
Uses Spring Boot JDBC and JavaFX, and builds with Maven.
Includes:
- Spring Boot JDBC for data access
- Optional AtlantaFX styling, remove the dependencies if you are not using it
- Inno Setup configuration for a custom non-admin installer
- CI/CD workflow to build and package the installer for release
- Windows 10/11 OS
- Java 25 (LTS)
- GraalVM 25, Windows x64
- Spring Boot 4.0.4
- JavaFX 22
- PostgreSQL 42.7.3
- Inno Setup 6.7.1
Install Visual Studio Build Tools and the Windows SDK. Follow the official GraalVM guide: https://www.graalvm.org/latest/getting-started/windows/#prerequisites-for-native-image-on-windows
- Download GraalVM binaries: GraalVM.org
version: GraalVM 25, Windows x64
- Extract binaries to your preferred location:
Example:
C:\GraalVM\graalvm-jdk-25.0.2+10.1
Verify the \bin path:
C:\GraalVM\graalvm-jdk-25.0.2+10.1\bin
- Set required environment variables using
Command Prompt:
Example:
setx GRAALVM_HOME "C:\GraalVM\graalvm-jdk-25.0.2+10.1"
setx JAVA_HOME "C:\GraalVM\graalvm-jdk-25.0.2+10.1"
setx PATH "%PATH%;%JAVA_HOME%\bin"Restart Command Prompt, then verify:
java --versionSample output expectation:
java 25.0.2 2026-01-20 LTS
Java(TM) SE Runtime Environment Oracle GraalVM 25.0.2+10.1 (build 25.0.2+10-LTS-jvmci-b01)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 25.0.2+10.1 (build 25.0.2+10-LTS-jvmci-b01, mixed mode, sharing)
Note
GraalVM is used to build the native image. The application runs as a native executable and does not require a separate JVM.
- Navigate to the project directory:
cd <your-repo-directory>- Create the
devconfiguration:
Copy the example file and rename it:
cp src\main\resources\application-dev.yml.example src\main\resources\application-dev.ymlOpen src\main\resources\application-dev.yml and replace placeholders with your settings.
- Set active profile (points to dev configuration):
Powershell:
$env:SPRING_PROFILES_ACTIVE="dev"Command Prompt:
set SPRING_PROFILES_ACTIVE=dev- Build the application JAR:
.\mvnw clean packageBuilds the application and runs TestFX tests in headless mode using Monocle.
- Generate Spring Ahead-of-Time (AOT) Artifacts and Reachability Metadata:
.\mvnw test -Pnative-traceThis attaches the GraalVM tracing agent to TestFX to collect reachability metadata automatically. Note that it will take control of your I/O because the tracing agent requires a non-headless UI.
Warning
Ensure your tests behave like end-to-end tests and exercise ALL UI flows through real user interactions.
- Build the native executable:
.\mvnw package -Pnative- Build the installer via CLI (Inno Setup):
iscc installer.issIf iscc is not in your PATH, use the full path to the compiler, for example:
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer\installer.issSkip tests by adding -DskipTests to any of the above Maven command.
For local development, use the run configuration in the .run directory or run manually:
.\mvnw javafx:runThis builds the JAR and runs the application.
The CI/CD workflow requires database credentials stored as GitHub Secrets.
- Go to your repository on GitHub
- Open Settings → Secrets and variables → Actions
- Add the following
Repository secrets:
SPRING_DATASOURCE_JDBCURLSPRING_DATASOURCE_USERNAMESPRING_DATASOURCE_PASSWORD
Use the same values as your local application-dev.yml.
Example:
SPRING_DATASOURCE_JDBCURL=jdbc:postgresql://localhost:5432/your_databaseSPRING_DATASOURCE_USERNAME=your_usernameSPRING_DATASOURCE_PASSWORD=your_password
These secrets are used by the workflow to run tests, build the native executable, and package the installer.
Note
A video to show the usage. Database setup instructions are not included.
Why use Spring Boot JDBC instead of JPA?
JPA relies on Hibernate ORM, which depends on reflection. GraalVM has strict constraints around reflection, which makes Hibernate integration notoriously difficult. Therefore, JDBC is the more viable choice, as it provides greater control and stability.
Attempts to work around these limitations require extensive configuration and often fail. I spent way too much time trying to make this work without success.
A potentially useful reference for enabling Hibernate with GraalVM is:
And it's associating forum discussion:
https://discourse.hibernate.org/t/how-to-correctly-disable-byte-buddy-for-graalvm-native-image/12163
This example uses Hibernate and HikariCP directly. It does not include Spring Boot.
This project is licensed under the MIT License.