Example: How to use javapackager to build a MacOS application bundle

I recently learned how to use the Java javapackager command to build a macOS application bundle — i.e., a regular macOS application — from a Java application. In this tutorial I’ll show how to create a Mac application bundle from a simple Java class, in this case a Java Swing class.

Back to top

Building a MacOS application bundle with javapackager

The short story of how to build a macOS application bundle using javapackager in this project is to use these steps.

First, clone my Github project onto your macOS system:

Next, move into that directory. To compile and run the project, run these commands:

  • Compile the Java class with ./1compile.sh
  • Create a JAR file from the *.class files and manifest with./2makeJar
  • Build the Mac application bundle with ./3build

Assuming that everything works as it should, you can then run the newly-built application using this command:

  • open release/bundles/MacJavaProperties.app/

When you run the application like this, you should see the following Mac/Java application open up:

A macOS application bundle built with javapackager

Back to top

The longer story

For the longer version of the story, read on ...

Back to top

The Mac/Java class

I named the Java class file MacJavaPropertiesApp.java, and its source code looks like this:

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.util.Map;
import java.util.Properties;
import java.util.Enumeration;

public class MacJavaPropertiesApp {

    public static void main(String[] args) {
        new MacJavaPropertiesApp();
    }

    public MacJavaPropertiesApp() {
        SwingUtilities.invokeLater(() -> {

            // system properties
            String s = "\nSYSTEM PROPERTIES\n";
            Properties p = System.getProperties();
            Enumeration keys = p.keys();
            while (keys.hasMoreElements()) {
                String key = (String)keys.nextElement();
                String value = (String)p.get(key);
                System.out.println(key + ": " + value);
                s = s + key + ": " + value + "\n";
            }

            // environment variables
            s = s + "\n\nENVIRONMENT VARIABLES\n";
            Map<String, String> env = System.getenv();
            for (String envName : env.keySet()) {
                s = s + "\n";
                s = s + envName + ": " + env.get(envName);
            }
    
            JTextArea textArea = new JTextArea();
            textArea.setText(s);
            JScrollPane scrollPane = new JScrollPane(textArea);

            JFrame frame = new JFrame("Mac/Java Properties");
            frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(new Dimension(600, 400));
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

}
Back to top

The three scripts

The first script is named 1compile.sh, and here’s its source code:

javac MacJavaPropertiesApp.java
echo "compiled MacJavaPropertiesApp.java"

The second script is named 2makeJar, and here’s its source code:

jar cmf manifest MacJavaPropertiesApp.jar *.class
echo "created MacJavaPropertiesApp.jar from MacJavaPropertiesApp.java and manifest"

The third script is named 3build. It contains the javapackager command, and here’s its source code:

# SEE https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javapackager.html

JAVA_HOME=`/usr/libexec/java_home -v 1.8`
APP_DIR_NAME=MacJavaProperties.app

#-deploy -Bruntime=/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home \
javapackager \
  -deploy -Bruntime=${JAVA_HOME} \
  -native image \
  -srcdir . \
  -srcfiles MacJavaPropertiesApp.jar \
  -outdir release \
  -outfile ${APP_DIR_NAME} \
  -appclass MacJavaPropertiesApp \
  -name "MacJavaProperties" \
  -title "MacJavaProperties" \
  -nosign \
  -v

echo ""
echo "If that succeeded, it created \"release/bundles/${APP_DIR_NAME}\""

Note that the -appclass setting should refer to the full path of the main class, such as com.acme.MyApp. In this case I didn’t put the main class in a package, so I didn’t have to specify that information.

Back to top

javapackager notes

There are about 100 things I could say about the javapackager command, but here are a few notes:

  • I don’t specify an application, but you declare one with icon=path
  • If you want to sign your application, that takes much more work, including needing an Apple developer account, and generating certificates
  • You can also specify JVM options and Mac-specific options

Those are the first things that come to mind. I’ll add more notes as I think about them.

One final note: This is intended to be a relatively simple javapackager example. As I mentioned, there are many, many more things to say about it, but this example shows how to create a macOS application bundle from Java source code, which is what I wanted to show today. (More complicated examples will have multiple JAR files, application resource files, etc.)

Back to top

Add new comment

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.