For an app already in the app store we use a modified version of GraphHopper, a Java based routing engine for directions. We also use a minimized version of GraphHopper on iOS for offline routing.
For years we've been using GitHub - google/j2objc: A Java to iOS Objective-C translation tool and runtime. to translate GraphHopper to objc, but we're beginning into run into issues - for example Xcode Previews only working in legacy mode with our GraphHopper library added to the project - so I'm investigating alternatives and started playing with Swift-Java.
@ktoso confirmed that iOS is not supported - so starting a conversation, so that maybe in future this would be an option.
I guess the idea would be to build an SPM that includes a (minimized?) JRE, the GraphHopper.jar, and the wrapper that the Swift-Java plug-in generated to call into GraphHopper.
And all this adhering to the App Store Review Guidelines.
In the distant past (circa 2012) I released a few iOS apps with an embedded JVM that handled my specific need: loading JDBC drivers to enable the apps to connect to a variety of enterprise relational databases. I started with SableVM, and later migrated it to JamVM. They are (or were — those projects seem rather dormant now) both simple clean-room virtual machines written in C, and so were amenable to embedding and distributing as part of an app. They were both interpreter-only, but that was fine since iOS blocks JIT anyway.
The app size would be quite large, since the GNU Classpath jars needed to be shipped for runtime support. I was able to reduce the size by stripping out a lot of stuff that would never be used (CORBA classes and the like). Some work was needed to port the native calls to work with iOS (and work around sandboxing issues), but I recall it wasn't too bad. More recent versions of Java might be considerably more complex, but Java 1.5 was sufficient for my needs.
The JNI glue for communicating between Objective-C and Java was hand-written; that pain led me to create Kanji for handling the binding creation, which was an early predecessor for the code that now powers Skip's Swift-Kotlin bridging on the Android side. These days, the JavaKit macros in the swift-java project would probably be a good fit for that sort of thing.
All that is to say: it is possible, but a lot of work and I'm not sure I would recommend it when you have access and control over the original source code you are trying to run on iOS. On the other hand, if you like the idea of tinkering with JVM internals, have at it! It would make a neat community project, and I learned a ton when I was working on it.
Do please file a feedback with previews diagnostics about this. We want to know why this isn't working but we'll need logs following the steps suggested here in Apple's developer forums.
If they are running j2objc from an Xcode build script, then it is probably failing because Xcode previews run the build script every time the preview is updated, and there is no way to check for it in the script since the environment variable ENABLE_PREVIEWS was no longer an indicator of a preview build in Xcode 16.
This has been discussed by myself and others on the developer forums (Xcode 16 no longer sets ENABLE_PRE… | Apple Developer Forums). I filed a feedback report (FB16424982, closed with "Works as Designed”), and opened a DTS ticket (11213728, last correspondence 4 months ago: "We’ll review your request"). The "Use Legacy Previews Execution" workaround sometimes works, but other time is just mysteriously absent from the menus; it is also silently reset every time Xcode upgrades.
I'm excited that there may now be progress on this bug! It has been a crippling issue for our users.
Embedding a JVM in an iOS app sounds like all kinds of problems and it's not something we're investigating.
swift-java though does generate sources that are swift wrappers around java types (see the JavaKit macros and tools around it), so if that's any help you could be using that. I don't feel like I can endorse bringing a JVM into an app though, but that's your call what you make with that.
Technically you could look into Graal and nativeimage tools to make a "shared (native) library" for the java library, but I have no idea how realistic that is (or if licensing and various rules would see this as ok or not).
Build script phases that alter build inputs are problematic in general and will not be able to work with Previews. This isn't Previews specific. Any script that alters build inputs causes the build system to invalidate its build description every time, increasing build times among other correctness issues. Previews just happens to be caught in the mess because it now re-uses the exact same build description as build-and-run and there's no distinction any more in the build settings.
At best, make sure your build scripts are truly idempotent, that is they should not alter the mod times if the contents are identical. Running the script twice in a row should be a true no-op. That at least minimizes the mess and ensures that the build system doesn't think it needs to build more than twice after the script makes modifications.
Edit:
Also, it looks like the missing "Use Legacy Previews Execution" menu item is fixed in Xcode 16.3.
Thanks for all the comments so far - very interested in reading how everyone has approached/would approach this problem.
As our j2objc Previews problem discussion:
The j2objc pipeline is not part of the Xcode project at all. It's a separate script we run which generates a bunch of xcframeworks for us. These frameworks are then used just as any other framework in the app.
The problem that Xcode Preview has is that it sees some "duplicate symbol" when these frameworks are in the project. Normal build and run and legacy Preview mode is fine - my first thought was that maybe the "normal" processes do some form of stripping or namespacing that Preview mode does not, hence we only encounter the problem in Preview mode. Buts its a theory I never explored - it might be something completely different.
If there's interest in the Preview problem I'll see what I can do to submit the requested debugging information and maybe a sample Xcode project demonstrating the problem - but its not the only reason we're exploring alternatives: j2objc is having increased issues transpiling newer versions of GraphHopper out of the box, which is why we're exploring other paths.
If running code from a jar on iOS is not the way Swift-Java is heading I do understand and so be it - I am aware of the many downsides this has, but I do hope other developers appreciate that being able to run an exact replica of the same full directions routing engine we use online, including reading in mapping data, calculating routes with variable routing profiles, output in the format we already use - is a really useful thing - even if it does consume more disk space and memory. Rewriting GraphHopper in Swift and keeping it on par With GraphHopper would be a giant task. Finding a C based routing engine and then converting the data to its format and then back to ours would also be an option, but would offer users a different routing experience simply because the C based engine would route differently. This might be better or worse, but definitely wouldn't be the same.