Make an Mac OSX App Bundle for your Java Application

Java programs are often distributed as runnable jar files. The idea is that the same jar can be run on any platform. Of course, the core tenet behind ‘write-once-run-everywhere’ is giving users of each platform a slightly different, yet mediocre user experience. I’ll address how to improve this, at least from the Mac side.

Problems with the Runnable Jar Approach:

Inconsistency – When Mac users want to run a program, they expect an application bundle, a .app. Windows users expect a .exe file. Having a .jar file run a program is strange to most people. Double-clicking a jar does run it on a Mac, but on windows, it unzips it.. uh huh.

More inconsistency – Okay, so a .jar is an application, right? Let me go find one and double-click on it:

Well, it turns out that a jar file can also be a library. In fact, most of them are. The Lightweight Java Gaming Library is not a program you can run. So, the fact is, you’ll never know if your .jar is an application you can run, or a library for a developer to do programmy things with.

Improper Application Behavior – When you double click a .jar, you’re actually running the java executable. So if you right click whatever shows up, on your dock and choose “Show in Finder”, you get sent somewhere you don’t want to be, (like /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java).

Icons! – Whatever icon you give to your .jar file is ignored. Instead you get a cup of coffee. Not a great way to impress your users, and it’ll probably confuse a lot of them.

All-in-one-ness – Mac users have come to expect an application be self contained. If you are distributing a reasonably complicated java application, you’ll have to include your dependencies in a folder (they are also .jars), since you can’t put them in the jar that your users run. You can put your other assets in that jar though, like your images.

The Solution: Making Real Mac OS X Application Bundles

The solution is to distribute your app on the Mac in an Application Bundle. Fortunately, this isn’t toooo hard. Apple provides a tool called Jar Bundler for this purpose, and it has some fairly solid documentation.

I’m going to walk through the tutorial in a worst case scenario. You want to bundle up a game with a bunch of libraries, including Slick (a nice 2D Java Game library), and LWJGL, which wraps OpenGL, does other stuff, and has native libraries.

I’m going to assume you have a working ‘double-clickable jar’. Launch Jar Builder and fill out the first page, like this:

There’s nothing too complicated here. You just specify the Main Class, just like in your jar manifest file. Let’s check out the classpath and files tab:

So I’ve added all my Jars.. yeah, there are a lot of them. Most importantly, notice Client.jar, that’s the one with my game actually inside it. Make sure that none of the classpath variables are absolute. If it comes up as /Users/yourname/something…well, that won’t be on your customer’s machine.

Now, this clearly isn’t everything your application will need! Maybe there’s a better way to do this, but I found it easier to build the bundle as is, and fix it up later. Keith seemed to think that was the way to go as well, and he’s one smart cookie, so in absence of other information, let’s go with that.

So, now the easy part is done. Your app will certainly not work. You are probably loading images and such, and this raises the question of the working directory of your application. Commonly, on a Mac, your various game assets should be stored inside the app bundle in Contents/Resources. For whatever reason, the Java Application Stub sets the working directory to the directory containing the app bundle. You’ll probably want to change that to the resources folder, so you can put your images and other resources inside the app bundle.

To change this, add this in the Java element of your Info.plist file, in your application bundle:

Finally, if your project is using native libraries, make sure they’re in the right place. You’ll find out if they aren’t… I’m using the LWJGL, and I had success leaving the native libraries in the $JAVAROOT, which is inside the bundle, in Contents/Resources/Java/.

So, one final review of what is inside my App Bundle:

Contents/Info.plist

Contents/MacOS

Contents/MacOS/JavaApplicationStub

Contents/PkgInfo

Contents/Resources/graphics/*.png, etc…

Contents/Resources/Java/MyCode.jar

Contents/Resources/Java/lwjgl.jar

Contents/Resources/Java/slick.jar

Contents/Resources/Java/myOtherLibraries.jar, etc…

Contents/Resources/Java/liblwjgl.jnilib, and any other native libraries.