I am looking for the path the the JRE file on Mac OS X 10.6.8. Or is there no such file? Is JRE just slang for an idea or concept? Java Runtime Environment is a real thing. Java for OS X is an Apple vetted Java environment, designed to be as highly integrated as possible within its own operating system. Java for OS X has been created to mix OS X's advanced technologies and Java's adaptability and omnipresence on almost all software platforms out there. Notice that the Java.runtime/ directory honors the standard bundle format. This is important if you plan to code-sign your application, in that codesign simply won’t work if the bundle isn’t a macOS bundle. When we built the JRE with jlink, the example should have produced a directory named “Home”; the Home directory above should be this entire directory.
- 2018-September-17
There are lots of tutorials explaining how to bundle a Java application as amacOS application, but there aren’t really any that explain how to bundle aJava application into an existing Objective-C or Swift Cocoa application.
This article describes one possible means of using Java within suchapplications, and will work for Swift as well as Objective-C.
Background to this Exercise
Since version 4.0, Balthisar Tidy and Balthisar Tidy for Workhave offered HTML document validation with the W3C Nu HTML Checker.Users are given the option to perform document validation with their choiceof the official W3C server, a server of their choice, or the localserver that’s bundled with Balthisar Tidy.
The local server, inconveniently, is a Java application, which makes it quitetricky to use from a C/Objective-C environment. While C interfaces remain thedefault for linking to foreign libraries (ask Ruby, Python, PHP, and everyoneelse), Java’s JNI can be a bit of a pain to implement on macOS. Thisgoes doubly for the Nu HTML Checker, because it’s designed to be a completeapplication used as a web service, and not a library like HTML Tidy.
The fact that it’s a web service, though, means that we can can communicatewith it via HTTP, just like we have to do with the W3C’s server oruser-specified server. This means, all we have to do is run it in a processseparate from Balthisar Tidy.
Adding the JAR
At first glance, this appears easy:
- After building the Nu HTML Checker, we have the entire application in asingle Java JAR file:
vnu.jar
. - Xcode and macOS already support Java in packages, so the framework targetthat implements the server (
JSDNuVFramework
in the case of BalthisarTidy) can simply put the JAR inJSDNuVFramework/Java/JAR
, and this is astandard build phase in Xcode. - And, I can run the JAR file by invoking Java, probably with NSTask.
Except this probably won’t work, and won’t even be allowed if you want tooffer your application on the Mac App Store.
You Need a JRE
Like both Objective-C and Swift, Java applications require a runtime, calledthe Java Runtime Environment (JRE). It used to be that you could almost counton this being installed on a macOS system by default, but that’s not been thecase in many, many releases.
Sometimes users install Java (which includes the JRE, naturally) on their ownin order to support other Java applications. If not, macOS will inform theuser to install Java when an attempt to use Java is made, but it’s up to theuser to find and install a compatible Java version.
Counting on the system to prompt to install Java isn’t very friendly, and it’sverboten for Mac App Store applications.
Instead, your only alternative is to host the JRE within your application’sbundle (or in the case of
JSDNuVFramework
, the framework bundle, which isfound in the application bundle). The rest of this article will explain how todo that while minimizing the JRE size.The JRE can Bloat your Application
The entire Java 10 JRE can be over 540 MB! While you can certainly bundle theentire JRE in your application, you’ll probably find that your users willwonder why the download size for your simple date calculator (or whatever) ishalf a gigabyte.
The entire JRE consists of headers, debugging symbols, documentation, andexecutable code that your application – in all likelihood – does’t require.Using Java’s own tools, we can find out the true JRE dependencies and builda custom JRE that’s significantly smaller. In the case of
vnu.jar
, the JREhas been trimmed down to just under 45 MB for Balthisar Tidy’s next release.Prerequisites
If you’re reading this article for information (rather than for fun), youprobably already have a Java JDK installed. These instructions should workfor Java 9 and anything newer than Java 10, but I’ve only tested thesetechniques with Java 10.
Don’t overlook setting your
$JAVA_HOME
environment variable. It makes itmuch easier to type long paths in Terminal.app, and it means I won’t haveto type long paths in this article that, when you copy and paste in your ownterminal, fail to work.On my system, I've set
…in my
.bash_profile
file.Determine JRE Dependencies
To determine which parts of the JRE that
vnu.jar
depend on, use the jdeps
tool provided by Java. Note that my terminal’s working directory is the rootdirectory of the Nu HTML Checker project; adjust your paths to your ownsituation.For the most part, it’s okay to ignore the “split package” warnings. Theimportant data is the last line of output:
These are the Java modules that must constitute our custom JRE.
Build the Custom JRE with jlink
Next, we can build the custom JRE, and make it as small as possible in onelong step. Again, my example is working from the repository’s root directory.
The result will be a custom JRE with the minimum number of Java modulesrequired to run the
vnu.jar
JAR. Things such as debug symbols and headerswill be omitted, and everything will be packaged into the single folder named“Home”.Now Construct Our Package
Xcode and macOS will expect your JRE to be installed in your bundle’s standard“PlugIns” folder, and that it follow the standard bundle format itself. Ifyou are planning to code sign your application, then this bundle format isdoubly important.
Create this directory structure manually in Finder or your terminal.In the case of
JSDNuVFramework
, our bundle content looks something like this:Notice that the
Java.runtime/
directory honors the standard bundle format.This is important if you plan to code-sign your application, in thatcodesign
simply won’t work if the bundle isn’t a macOS bundle.When we built the JRE with
jlink
, the example should have produced adirectory named “Home”; the Home
directory above should be this entiredirectory. Just drag it over in Finder to keep things simple.However, let’s address the
MacOS
directory and its libjli.dylib
library.This file is not generated by the JRE, and it’s not part of the JRE. Rather,it’s a bridging library required by macOS to execute the java
instance inthe bundle. You should copy and paste this from your JDK installation path,which should be $JAVA_HOME/lib/jli/libjli.dylib
.If you wish to pare down your JRE just a little bit further, feel free todelete all of the executables except for
java
in the Home/bin/
directory.If you plan to sign your executable, do this before signing.If you’re not code-signing or deploying to the Mac App Store, then you’redone. You can use
NSTask
to execute the java
executable in your bundleusing vnu.jar
as its argument.Change the java
Info.plist
Java Jre For Macos
Yes, binary executables can have an
Info.plist
file embedded into them,too. Let’s take a look at your custom JRE’s java
executable’s Info.plist
,adjusting your own paths accordingly:The result should be something very similar to this:
That
CFBundleIdentifier
with value net.java.openjdk.cmd
is going to bea problem for you if you wish to deploy to the Mac App Store, because everybundle in the Mac App Store must have a unique name. Some unknown developerhas already apparently submitted an application using this bundle identifier,and so it’s no longer available for use.You can develop a bash script to use
xxd
and sed
to convert the binary totext, and find and replace the net.java.openjdk.cmd
string, and convert thefile back to binary, but the simplest thing to do is probably use any binaryeditor and search for the offending bundle ID, and replace it with a newbundle ID of the exact same length. Please don’t use com.balthisar.java-a
or com.balthisar.java-b
, because I already have them in use!Give Entitlements to the java
executable
Java Jre 8 Mac Os X
Mac App Store apps must be sandboxed, which means that they only have theentitlements requested by the developer. In Xcode, there’s a simple GUI toset these for your application, but there’s no GUI for embedded executables.Because we have to execute
vnu.jar
with the JRE’s java
executable, itmust be given its own entitlements; it will not inherit your application’sentitlements by default.Although you can set any entitlements you want with the following method, thisexample will keep things simple and demonstrate how to inherit the containingapplication’s entitlements.
Create a text file named
java.entitlements
with the following contents:And then grant these entitlements to the
java
executable:Of course, use your own developer certificate, and as always, use the pathsappropriate to your situation.
Because this is a cryptographic operation, make sure any changes to the
Info.plist
are already completed prior to this step.Configure Xcode
Make sure that your application or framework target will copy and sign thisPlugIn bundle when you build. Do this by adding a 'New Copy Files Phase'to the target. Rename it to something sensible (such as “Copy Java Runtime”),choose PlugIns as the destination, choose the
Java.runtime
directory,and ensure that code sign on copy is selected.Download Java For Mac Os
Sign the Java.runtime
bundle
Sometimes code sign on copy will complain that the
Java.runtime
bundleis not already signed. Although this appears to have gone away with Xcode 10,you may have to manually deep sign the bundle in order to have Xcode re-signit when building your project:As usual, check your own developer ID and paths to the package.
Launch your NSTask
Macos Java Jre
That’s it! Assuming you know how to locate the resources in your bundle,you can run your JAR file using NSTask. To see how Balthisar Tidy doesthis, including a watchdog to kill the process if Balthisar Tidy dies,check out the source code for JSDNuVFramework on the Github page.