Goal of this task is to deploy a Java GUI application (AWT, Swing, JavaFX) as the macOS application bundle.
- The application can be install by copying it into the Application folder.
- It can be launched from Spotlight.
- It can be kept in the Dock.
Implementation
To do this we will have to use Xcode to implement a wrapper application, which will launch the Java application as a sub-process.
- Create new Project -> macOS -> App
- Specify a name of the app, and choose interface "SwiftUI", and language "Swift".
- In the project create new group (right click on the application dir -> New Group) called
Resources
. After that copy the Java app's Jar file into this group. - Add Jar file to deployed files by clicking on the project name -> Build Phases -> Copy Bundle Resources -> add Jar file.
- Optional: By default the wrapper application will run in a sandbox. This means it won't be able to access files in arbitrary locations on a disk. The simples way to mitigate this is to remove the sandbox in Signing & Capabilities -> remove App Sandbox.
Locate a Swift file with the main class. Such a class is usually annotated with @main
or @UIApplicationMain
annotations. We will completely replace its code with a custom launcher.
All we need is to locate the Jar file in the application bundle, get a path to a Java executable and then spawn a sub-process.
@main
struct LauncherApp {
static func main() {
// Locate the Jar file in the launcher app bundle
let jarPath = Bundle.main.path(forResource: "my-app", ofType: "jar")
let task = Process()
task.launchPath = "/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/bin/java"
task.arguments = ["-jar", "(jarPath!)"]
task.launch()
task.waitUntilExit()
}
}
When the launcher app is completed, we can build it by Product -> Build.
The launcher app can be improved, for example it can search for an appropriate Java version by looking for the JAVA_HOME
environment variable, or by launching the /usr/libexec/java_home
command. Also, it can print potential error messages via NSAlert
.
Alternatives
-
The OpenIndex / JavaMacLauncher provides pre-compiled app launcher, configurable by the XML properties in the
Info.plist
file. This launcher is implemented in Go. -
The tofi86 / universalJavaApplicationStub provides similar pre-build app launcher implemented as a BASH script.
Issue with this solution is that on latest macOS versions, the launcher app needs to have some elevated privileges, and since this is a BASH script, that means
/bin/bash
has to have those privileges, which may be problematic in some cases.