# How to bundle Java in a universal MacOS app

**URL:** <https://forum.vassalengine.org/t/how-to-bundle-java-in-a-universal-macos-app/77991>\
**Category:** Development\
**Created:** [October 4, 2023, 9:30pm UTC](https://forum.vassalengine.org/t/how-to-bundle-java-in-a-universal-macos-app/77991 "2023-10-04T21:30:31Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![uckelman](https://forum.vassalengine.org/user_avatar/forum.vassalengine.org/uckelman/32/10020_2.png) [@uckelman](https://forum.vassalengine.org/u/uckelman)\
**Post date:** [October 4, 2023, 9:30pm UTC](https://forum.vassalengine.org/t/how-to-bundle-java-in-a-universal-macos-app/77991/1 "2023-10-04T21:30:32Z")

</div>

Vassal 3.7.2 will have a single universal MacOS bundle, runnable on both Intel and Apple Silicon Macs. It will no longer be necessary to pick (or let the front page of our website guess) the correct one for your architecture. The only one we provide from here onward will work natively on _both_.

It’s not at all obvious how to build a universal MacOS bundle for a Java application, and there’s little information on the internet about doing it. Here’s what we learned:

There are no universal JDKs out there for MacOS, so you need to get an x86\_64 one and an arm64 one and combine them using `lipo`. Our build process was already fetching the two JDKs we needed; we added a version of `lipo` which runs on Linux for producing the universal binaries:

> **[GitHub - konoui/lipo: This lipo is designed to be compatible with macOS...](https://github.com/konoui/lipo)**
>
> This lipo is designed to be compatible with macOS lipo, written in golang.

You might think the obvious approach is to `lipo` the two JDKs and then build bundles normally with the universal JDK you’ve produced… and you would be completely wrong, because `jlink` does not work properly with universal binaries. What it does, at least as of Java 20, is give you binaries stripped back to a single architecture. That is—`jlink` and `lipo` do not commute. Instead, you need to produce a minimal JRE using `jlink` for _each_ of the two architectures and then run `lipo` on the results of `jlink`.

This blog post about bundling universal Java apps for MacOS was extremely helpful:

> **[Building a “universal” Java application for macOS](https://incenp.org/notes/2023/universal-java-app-on-macos.html)**

The author noted that executables run from a shell script launcher default to x86\_64 if that architecture is present in the executable, even on arm64 machines. To overcome this, he switched his launcher to a C program. We found that we were able to convince executables which are run from a shell script launcher to run with the correct architecture if we detected the CPU architecture and then ran them with `arch`, which lets you force executables to run with a specified architecture:

```bash
# determine the correct CPU architecture
if sysctl machdep.cpu.brand_string | grep -q Intel ; then
  ARCH=x86_64
else
  ARCH=arm64
fi

# fire it up
exec arch -$ARCH Contents/MacOS/jre/bin/java -classpath Contents/Resources/Java/Vengine.jar -Xdock:name=VASSAL -Xdock:icon=Contents/Resources/VASSAL.icns VASSAL.launch.ModuleManager "${ARGS[@]}"

```

Hopefully somebody finds this explanation helpful. Thank you to @marktb1961 and @JoelCFC25 for providing essential testing as we worked through this.
