I've been working on a Swift library that reimplements the Java API — starting with Java 1.0.2. The goal is source-level compatibility, so that Java code can be ported to Swift without a JVM.
Java 1.0.2 coverage is now complete, and some more:
java.lang — all classes, interfaces, exceptions and errors
java.io — full stream hierarchy
java.util — BitSet, Vector, Hashtable, Observable, etc.
java.net — sockets, URLs
java.awt — full AWT including peer interfaces and image package
java.applet
The project is tagged as java-1.0-alpha. Work on Java 1.1 continues on main.
Release 0.51 provides an AWTShowcase application for macOS (over SwiftUI), Windows (over GDI) and Linux (over X11)
Perfect Complements, Not Competitors!
We shouldn't look at these two technologies as rivals. In a modern enterprise architecture, they actually complement each other perfectly:
On the Server: You use Apple’s swift-java bridge to communicate directly with your existing, massive Java backend infrastructure.
On the Client (iOS/Mac App): You use the code ported via JavApi4Swift to run the exact same validation or calculation logic offline and natively on the user's device.
Of course reference cycles must be handled. At the moment it is more a proof of concept and with the short running task I did not have memory problems (only tested under macOS).
I use Swift 6.3 and concurrency is "not easy", but no showstopper. It works.
Unchecked exception is horrible. The solution is to make this checked. But is also horrible in Swift to write every statement with single `try`. Not much where Java seems better, but try-blocks are developer friendly.
I understand it’s a PoC. But I’m an architect and I try to identify roadblocks early Declaring weak references will help with cycles, but that becomes difficult when the cyclic refs are in a library class where you can’t annotate them. For example, two objects each of which has a Vector containing a reference to the other object.
Many RuntimeExceptions can become fatal errors in Swift instead of throwing. The more modern approach to error handling (also used in Rust and sort of in Go) is to treat programmer errors like nil pointers and array overflow as bugs that should kill the process.
Weak references are not every time a solution, for example java.util.logging use weak references for the named Logger. If GC means to clean up your Logger reference you can use same name (string) and gets other Logger. But this is not a really problem, the problem is then a developer (or a vibe AI) have no knowledge of internal.
Your example is more an implementation problem; a good documented implementation problem how to works with pointers/references in cycling. How to cleanup these cycles.
IMHO: Kill the process is not a good solution for the end user. The developer should be handling this. The Java Throwable system is not bad constructed but often used or implemented, because you have two types with Error and Exception, and Exception should be not critical. Of course NullPointerException should be more an NullPointerError in my opinion.
It isn’t, and such errors — just like bus errors — should not occur in a properly tested released program. If they do, it means something has gone off the rails and it’s potentially dangerous for the program to continue.
That may sound weird to a Java developer, but the current (mostly)consensus is that altering the language semantics to make such errors recoverable isn’t worth it. If they were catchable, nearly everything in Swift would have to be marked as “throws”, making the “try” keyword so common as to be useless.
If or if not kill a process is off topic, Java have Errors and Exception you need to handle and RuntimeException to kill you application every time or perhaps not
You can now also in Java and Swift write
try { // or do in Swift
…
}
catch Throwable { // or _ in Swift
}
But in Swift you need to use write `try` in this block. But try is also useless, because no one is forcing you to think about why you use it. IMHO: A good try syntax can be like the guard like ``try something else errorIsComing``, because problem handling is near by problem. Java has this syntax introduced at some point for autoclose resources.
It may sound weird to all young programmer, but COBOL is some of the easiest language and I like the 88er concept - I`m sorry, I couldn't help it.
You can’t catch Throwable in Swift, because Throwable includes the RuntimeErrors that are fatal aborts in Swift.
The value of requiring try, in my experience, is that it makes it very clear where the flow of control can stop and exit the function (or jump into the nearest catch.) This is very much like the way await shows all the points where control can suspend. It really helps when reviewing code, your own or somebody else's.
It also means that if you change a function from non-throwing to throwing — which is a significant change to its signature and contract — you have to update all the places where it’s called, which in turn makes you consider whether and how to handle the exception.
Of course the contract of Java is: Hope never RuntimeExceptions is throwing.
At this point Swift is little better. But I hope you ever catch also RuntimeExceptions because Java eventually you get a memory leak, if GC not free all resources.
In my current implementation it works:
Maxmilian:JavApi4Swift$ head -n 29 Sources/SwingShowcase/SwingShowcase.swift && swift run SwingShowcase